From 274f785e1f253bba076ce256dc001203748b84c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Papierski?= Date: Tue, 7 Jul 2020 12:23:31 +0200 Subject: [PATCH 1/8] NDRS-90: Initial copy over of contract runtime. Source: Main repo at commit cc282a6. --- .gitignore | 131 +- Cargo.lock | 2818 +++++++++- Cargo.toml | 105 +- Makefile | 271 + .../.rpm/casperlabs-engine-grpc-server.spec | 64 + grpc/server/Cargo.toml | 68 + grpc/server/README.md | 14 + grpc/server/build.rs | 63 + grpc/server/debian/postinst | 33 + .../casperlabs-engine-grpc-server.service | 13 + .../protobuf/google/api/annotations.proto | 31 + grpc/server/protobuf/google/api/http.proto | 376 ++ .../protobuf/google/protobuf/descriptor.proto | 885 ++++ .../protobuf/google/protobuf/empty.proto | 52 + .../casper/consensus/consensus.proto | 311 ++ .../io/casperlabs/casper/consensus/info.proto | 161 + .../casperlabs/casper/consensus/state.proto | 364 ++ .../casperlabs/comm/discovery/kademlia.proto | 24 + .../io/casperlabs/comm/discovery/node.proto | 12 + .../casperlabs/comm/gossiping/gossiping.proto | 151 + .../protobuf/io/casperlabs/ipc/ipc.proto | 504 ++ .../io/casperlabs/ipc/transforms.proto | 51 + .../io/casperlabs/node/api/casper.proto | 209 + .../io/casperlabs/node/api/control.proto | 23 + .../io/casperlabs/node/api/diagnostics.proto | 26 + .../io/casperlabs/storage/storage.proto | 35 + .../src/engine_server/mappings/ipc/bond.rs | 47 + .../engine_server/mappings/ipc/deploy_item.rs | 73 + .../mappings/ipc/deploy_result.rs | 256 + .../engine_server/mappings/ipc/exec_config.rs | 68 + .../mappings/ipc/executable_deploy_item.rs | 140 + .../mappings/ipc/execute_request.rs | 70 + .../mappings/ipc/execution_effect.rs | 42 + .../mappings/ipc/genesis_account.rs | 59 + .../mappings/ipc/genesis_config.rs | 46 + .../src/engine_server/mappings/ipc/mod.rs | 16 + .../mappings/ipc/query_request.rs | 35 + .../mappings/ipc/run_genesis_request.rs | 45 + .../mappings/ipc/upgrade_request.rs | 54 + .../engine_server/mappings/ipc/wasm_costs.rs | 55 + grpc/server/src/engine_server/mappings/mod.rs | 148 + .../engine_server/mappings/state/account.rs | 154 + .../engine_server/mappings/state/big_int.rs | 136 + .../engine_server/mappings/state/cl_type.rs | 144 + .../engine_server/mappings/state/cl_value.rs | 43 + .../engine_server/mappings/state/contract.rs | 83 + .../mappings/state/contract_package.rs | 208 + .../mappings/state/contract_wasm.rs | 32 + .../src/engine_server/mappings/state/key.rs | 73 + .../src/engine_server/mappings/state/mod.rs | 18 + .../engine_server/mappings/state/named_key.rs | 85 + .../mappings/state/protocol_version.rs | 43 + .../engine_server/mappings/state/semver.rs | 19 + .../mappings/state/stored_value.rs | 76 + .../src/engine_server/mappings/state/uref.rs | 104 + .../mappings/transforms/error.rs | 65 + .../engine_server/mappings/transforms/mod.rs | 9 + .../mappings/transforms/transform.rs | 122 + .../mappings/transforms/transform_entry.rs | 55 + .../mappings/transforms/transform_map.rs | 66 + grpc/server/src/engine_server/mod.rs | 512 ++ grpc/server/src/lib.rs | 1 + grpc/server/src/main.rs | 393 ++ grpc/test-support/Cargo.toml | 41 + grpc/test-support/README.md | 14 + grpc/test-support/src/account.rs | 52 + grpc/test-support/src/code.rs | 34 + grpc/test-support/src/error.rs | 35 + .../src/internal/additive_map_diff.rs | 195 + .../src/internal/deploy_item_builder.rs | 249 + .../src/internal/exec_with_return.rs | 172 + .../src/internal/execute_request_builder.rs | 124 + grpc/test-support/src/internal/mod.rs | 89 + .../src/internal/upgrade_request_builder.rs | 103 + grpc/test-support/src/internal/utils.rs | 199 + .../src/internal/wasm_test_builder.rs | 719 +++ grpc/test-support/src/lib.rs | 94 + grpc/test-support/src/session.rs | 166 + grpc/test-support/src/test_context.rs | 190 + grpc/test-support/src/value.rs | 41 + grpc/test-support/tests/version_numbers.rs | 4 + grpc/tests/Cargo.toml | 77 + grpc/tests/benches/transfer_bench.rs | 325 ++ grpc/tests/src/lib.rs | 3 + grpc/tests/src/logging/metrics.rs | 95 + grpc/tests/src/profiling/README.md | 141 + .../src/profiling/concurrent_executor.rs | 419 ++ .../src/profiling/concurrent_executor.sh | 95 + .../src/profiling/host_function_metrics.rs | 332 ++ grpc/tests/src/profiling/mod.rs | 77 + grpc/tests/src/profiling/perf.sh | 54 + grpc/tests/src/profiling/simple_transfer.rs | 122 + grpc/tests/src/profiling/state_initializer.rs | 97 + grpc/tests/src/test/check_transfer_success.rs | 210 + .../contract_api/account/associated_keys.rs | 85 + .../contract_api/account/authorized_keys.rs | 412 ++ .../account/key_management_thresholds.rs | 74 + .../src/test/contract_api/account/mod.rs | 4 + .../test/contract_api/account/named_keys.rs | 110 + .../src/test/contract_api/create_purse.rs | 163 + grpc/tests/src/test/contract_api/get_arg.rs | 85 + .../src/test/contract_api/get_blocktime.rs | 27 + .../tests/src/test/contract_api/get_caller.rs | 108 + grpc/tests/src/test/contract_api/get_phase.rs | 40 + .../src/test/contract_api/list_named_keys.rs | 44 + .../tests/src/test/contract_api/main_purse.rs | 72 + .../tests/src/test/contract_api/mint_purse.rs | 48 + grpc/tests/src/test/contract_api/mod.rs | 16 + grpc/tests/src/test/contract_api/revert.rs | 20 + grpc/tests/src/test/contract_api/subcall.rs | 262 + grpc/tests/src/test/contract_api/transfer.rs | 354 ++ .../contract_api/transfer_purse_to_account.rs | 255 + .../contract_api/transfer_purse_to_purse.rs | 201 + .../src/test/contract_api/transfer_stored.rs | 108 + .../test/contract_api/transfer_u512_stored.rs | 109 + grpc/tests/src/test/contract_context.rs | 377 ++ grpc/tests/src/test/contract_headers.rs | 125 + grpc/tests/src/test/counter.rs | 197 + grpc/tests/src/test/deploy/mod.rs | 3 + .../src/test/deploy/non_standard_payment.rs | 138 + grpc/tests/src/test/deploy/preconditions.rs | 121 + .../tests/src/test/deploy/stored_contracts.rs | 1208 +++++ grpc/tests/src/test/explorer/faucet.rs | 76 + grpc/tests/src/test/explorer/faucet_stored.rs | 143 + grpc/tests/src/test/explorer/mod.rs | 2 + grpc/tests/src/test/groups.rs | 921 ++++ grpc/tests/src/test/manage_groups.rs | 509 ++ grpc/tests/src/test/mod.rs | 12 + grpc/tests/src/test/regression/ee_221.rs | 27 + grpc/tests/src/test/regression/ee_401.rs | 32 + grpc/tests/src/test/regression/ee_441.rs | 74 + grpc/tests/src/test/regression/ee_460.rs | 41 + grpc/tests/src/test/regression/ee_468.rs | 25 + grpc/tests/src/test/regression/ee_470.rs | 64 + grpc/tests/src/test/regression/ee_532.rs | 47 + grpc/tests/src/test/regression/ee_536.rs | 27 + grpc/tests/src/test/regression/ee_539.rs | 29 + grpc/tests/src/test/regression/ee_549.rs | 28 + grpc/tests/src/test/regression/ee_550.rs | 92 + grpc/tests/src/test/regression/ee_572.rs | 95 + grpc/tests/src/test/regression/ee_584.rs | 40 + grpc/tests/src/test/regression/ee_597.rs | 44 + grpc/tests/src/test/regression/ee_598.rs | 94 + grpc/tests/src/test/regression/ee_599.rs | 349 ++ grpc/tests/src/test/regression/ee_601.rs | 87 + grpc/tests/src/test/regression/ee_771.rs | 36 + grpc/tests/src/test/regression/ee_803.rs | 155 + grpc/tests/src/test/regression/ee_890.rs | 78 + grpc/tests/src/test/regression/mod.rs | 20 + .../src/test/system_contracts/genesis.rs | 203 + .../src/test/system_contracts/mint_install.rs | 75 + grpc/tests/src/test/system_contracts/mod.rs | 7 + .../src/test/system_contracts/pos_install.rs | 137 + .../proof_of_stake/bonding.rs | 537 ++ .../proof_of_stake/commit_validators.rs | 75 + .../proof_of_stake/finalize_payment.rs | 222 + .../proof_of_stake/get_payment_purse.rs | 59 + .../system_contracts/proof_of_stake/mod.rs | 5 + .../proof_of_stake/refund_purse.rs | 70 + .../test/system_contracts/standard_payment.rs | 712 +++ .../standard_payment_install.rs | 44 + .../src/test/system_contracts/upgrade.rs | 644 +++ grpc/tests/src/test/upgrade.rs | 575 +++ grpc/tests/src/test/wasmless_transfer.rs | 495 ++ node/Cargo.toml | 115 + node/benches/trie_bench.rs | 71 + {src => node/src}/apps/client/main.rs | 0 {src => node/src}/apps/node/cli.rs | 0 {src => node/src}/apps/node/config.rs | 0 {src => node/src/apps/node}/logging.rs | 0 {src => node/src}/apps/node/main.rs | 0 {src => node/src}/components.rs | 1 + {src => node/src}/components/api_server.rs | 0 .../src}/components/api_server/config.rs | 0 .../src}/components/api_server/event.rs | 0 {src => node/src}/components/consensus.rs | 0 .../consensus/consensus_protocol.rs | 0 .../consensus_protocol/protocol_state.rs | 0 .../consensus_protocol/synchronizer.rs | 0 .../components/consensus/deploy_buffer.rs | 0 .../components/consensus/era_supervisor.rs | 0 .../src}/components/consensus/highway_core.rs | 0 .../highway_core/active_validator.rs | 0 .../consensus/highway_core/block.rs | 0 .../consensus/highway_core/evidence.rs | 0 .../highway_core/finality_detector.rs | 0 .../consensus/highway_core/highway.rs | 0 .../consensus/highway_core/state.rs | 0 .../consensus/highway_core/tallies.rs | 0 .../consensus/highway_core/test_macros.rs | 0 .../consensus/highway_core/validators.rs | 0 .../consensus/highway_core/vertex.rs | 0 .../components/consensus/highway_core/vote.rs | 0 .../components/consensus/highway_testing.rs | 0 .../src}/components/consensus/protocols.rs | 0 .../components/consensus/protocols/highway.rs | 0 .../src}/components/consensus/traits.rs | 0 node/src/components/contract_runtime.rs | 126 + .../src}/components/deploy_gossiper.rs | 0 .../src}/components/in_memory_network.rs | 0 {src => node/src}/components/pinger.rs | 0 {src => node/src}/components/small_network.rs | 0 .../src}/components/small_network/config.rs | 0 .../src}/components/small_network/endpoint.rs | 0 .../src}/components/small_network/error.rs | 0 .../src}/components/small_network/event.rs | 0 .../src}/components/small_network/message.rs | 0 .../src}/components/small_network/test.rs | 0 {src => node/src}/components/storage.rs | 0 .../src}/components/storage/config.rs | 19 +- {src => node/src}/components/storage/error.rs | 0 .../src}/components/storage/in_mem_store.rs | 0 .../src}/components/storage/lmdb_store.rs | 0 {src => node/src}/components/storage/store.rs | 0 node/src/contract_core.rs | 15 + .../contract_core/engine_state/deploy_item.rs | 41 + .../engine_state/engine_config.rs | 32 + node/src/contract_core/engine_state/error.rs | 106 + .../engine_state/executable_deploy_item.rs | 92 + .../engine_state/execute_request.rs | 45 + .../engine_state/execution_effect.rs | 16 + .../engine_state/execution_result.rs | 373 ++ .../src/contract_core/engine_state/genesis.rs | 285 ++ node/src/contract_core/engine_state/mod.rs | 1764 +++++++ node/src/contract_core/engine_state/op.rs | 45 + node/src/contract_core/engine_state/query.rs | 52 + .../engine_state/run_genesis_request.rs | 63 + .../engine_state/system_contract_cache.rs | 253 + .../contract_core/engine_state/transfer.rs | 289 ++ .../src/contract_core/engine_state/upgrade.rs | 116 + node/src/contract_core/engine_state/utils.rs | 86 + .../execution/address_generator.rs | 106 + node/src/contract_core/execution/error.rs | 182 + node/src/contract_core/execution/executor.rs | 622 +++ node/src/contract_core/execution/mod.rs | 12 + node/src/contract_core/execution/tests.rs | 69 + node/src/contract_core/resolvers/error.rs | 11 + .../resolvers/memory_resolver.rs | 11 + node/src/contract_core/resolvers/mod.rs | 34 + .../resolvers/v1_function_index.rs | 89 + .../contract_core/resolvers/v1_resolver.rs | 248 + node/src/contract_core/runtime/args.rs | 232 + node/src/contract_core/runtime/externals.rs | 672 +++ .../contract_core/runtime/mint_internal.rs | 92 + node/src/contract_core/runtime/mod.rs | 3515 +++++++++++++ .../runtime/proof_of_stake_internal.rs | 215 + .../runtime/scoped_instrumenter.rs | 152 + .../runtime/standard_payment_internal.rs | 76 + node/src/contract_core/runtime_context/mod.rs | 857 ++++ .../contract_core/runtime_context/tests.rs | 833 +++ .../contract_core/tracking_copy/byte_size.rs | 134 + node/src/contract_core/tracking_copy/ext.rs | 243 + node/src/contract_core/tracking_copy/meter.rs | 27 + node/src/contract_core/tracking_copy/mod.rs | 429 ++ node/src/contract_core/tracking_copy/tests.rs | 548 ++ node/src/contract_shared.rs | 21 + node/src/contract_shared/account.rs | 655 +++ .../account/action_thresholds.rs | 150 + .../account/associated_keys.rs | 329 ++ node/src/contract_shared/additive_map.rs | 169 + node/src/contract_shared/gas.rs | 186 + node/src/contract_shared/logging/README.md | 50 + node/src/contract_shared/logging/mod.rs | 188 + node/src/contract_shared/logging/settings.rs | 63 + .../logging/structured_message.rs | 453 ++ .../logging/terminal_logger.rs | 110 + node/src/contract_shared/motes.rs | 201 + node/src/contract_shared/newtypes/macros.rs | 119 + node/src/contract_shared/newtypes/mod.rs | 286 ++ node/src/contract_shared/page_size.rs | 15 + node/src/contract_shared/socket.rs | 34 + node/src/contract_shared/stored_value.rs | 237 + node/src/contract_shared/test_utils.rs | 43 + node/src/contract_shared/transform.rs | 750 +++ node/src/contract_shared/type_mismatch.rs | 23 + node/src/contract_shared/utils.rs | 79 + node/src/contract_shared/wasm.rs | 27 + node/src/contract_shared/wasm_costs.rs | 193 + node/src/contract_shared/wasm_prep.rs | 62 + node/src/contract_storage.rs | 28 + node/src/contract_storage/error/in_memory.rs | 26 + node/src/contract_storage/error/lmdb.rs | 49 + node/src/contract_storage/error/mod.rs | 4 + .../global_state/in_memory.rs | 356 ++ .../src/contract_storage/global_state/lmdb.rs | 342 ++ node/src/contract_storage/global_state/mod.rs | 219 + node/src/contract_storage/protocol_data.rs | 319 ++ .../protocol_data_store/in_memory.rs | 36 + .../protocol_data_store/lmdb.rs | 54 + .../protocol_data_store/mod.rs | 15 + .../protocol_data_store/tests/mod.rs | 1 + .../protocol_data_store/tests/proptests.rs | 60 + node/src/contract_storage/store/mod.rs | 45 + node/src/contract_storage/store/store_ext.rs | 46 + node/src/contract_storage/store/tests.rs | 49 + .../transaction_source/in_memory.rs | 157 + .../transaction_source/lmdb.rs | 102 + .../transaction_source/mod.rs | 58 + node/src/contract_storage/trie/gens.rs | 39 + node/src/contract_storage/trie/mod.rs | 328 ++ node/src/contract_storage/trie/tests.rs | 79 + .../contract_storage/trie_store/in_memory.rs | 131 + node/src/contract_storage/trie_store/lmdb.rs | 160 + node/src/contract_storage/trie_store/mod.rs | 18 + .../trie_store/operations/mod.rs | 940 ++++ .../trie_store/operations/tests/ee_699.rs | 410 ++ .../trie_store/operations/tests/keys.rs | 309 ++ .../trie_store/operations/tests/mod.rs | 862 ++++ .../trie_store/operations/tests/proptests.rs | 97 + .../trie_store/operations/tests/read.rs | 128 + .../trie_store/operations/tests/scan.rs | 160 + .../trie_store/operations/tests/write.rs | 657 +++ .../trie_store/tests/concurrent.rs | 120 + .../contract_storage/trie_store/tests/mod.rs | 73 + .../trie_store/tests/proptests.rs | 76 + .../trie_store/tests/simple.rs | 552 ++ {src => node/src}/crypto.rs | 0 {src => node/src}/crypto/asymmetric_key.rs | 0 {src => node/src}/crypto/error.rs | 0 {src => node/src}/crypto/hash.rs | 0 {src => node/src}/effect.rs | 0 {src => node/src}/effect/announcements.rs | 0 {src => node/src}/effect/requests.rs | 0 {src => node/src}/lib.rs | 4 +- node/src/logging.rs | 134 + {src => node/src}/reactor.rs | 0 {src => node/src}/reactor/non_validator.rs | 0 {src => node/src}/reactor/queue_kind.rs | 0 {src => node/src}/reactor/validator.rs | 11 + {src => node/src}/reactor/validator/config.rs | 0 {src => node/src}/reactor/validator/error.rs | 0 {src => node/src}/testing.rs | 0 {src => node/src}/testing/network.rs | 0 {src => node/src}/tls.rs | 0 {src => node/src}/types.rs | 0 {src => node/src}/types/block.rs | 0 {src => node/src}/types/deploy.rs | 0 {src => node/src}/utils.rs | 0 {src => node/src}/utils/gossip_table.rs | 0 {src => node/src}/utils/round_robin.rs | 0 smart-contracts/contract-as/.gitignore | 99 + smart-contracts/contract-as/.npmignore | 1 + smart-contracts/contract-as/.npmrc | 1 + smart-contracts/contract-as/README.md | 91 + .../contract-as/assembly/account.ts | 181 + .../contract-as/assembly/bignum.ts | 597 +++ .../contract-as/assembly/bytesrepr.ts | 399 ++ .../contract-as/assembly/clvalue.ts | 177 + .../contract-as/assembly/constants.ts | 34 + .../contract-as/assembly/contracts.ts | 0 smart-contracts/contract-as/assembly/error.ts | 193 + .../contract-as/assembly/externals.ts | 207 + smart-contracts/contract-as/assembly/index.ts | 631 +++ smart-contracts/contract-as/assembly/key.ts | 275 + smart-contracts/contract-as/assembly/local.ts | 53 + .../contract-as/assembly/option.ts | 78 + smart-contracts/contract-as/assembly/pair.ts | 41 + smart-contracts/contract-as/assembly/purse.ts | 134 + .../contract-as/assembly/runtime_args.ts | 27 + .../contract-as/assembly/tsconfig.json | 10 + smart-contracts/contract-as/assembly/unit.ts | 19 + smart-contracts/contract-as/assembly/uref.ts | 142 + smart-contracts/contract-as/assembly/utils.ts | 69 + smart-contracts/contract-as/build/.gitignore | 4 + smart-contracts/contract-as/index.js | 12 + smart-contracts/contract-as/package-lock.json | 4521 +++++++++++++++++ smart-contracts/contract-as/package.json | 48 + .../tests/assembly/bignum.spec.as.ts | 315 ++ .../tests/assembly/bytesrepr.spec.as.ts | 408 ++ .../tests/assembly/runtime_args.spec.as.ts | 46 + .../tests/assembly/utils.spec.as.ts | 68 + .../contract-as/tests/bignum.spec.ts | 3 + .../contract-as/tests/bytesrepr.spec.ts | 3 + .../contract-as/tests/runtime_args.spec.ts | 3 + .../contract-as/tests/tsconfig.json | 7 + .../contract-as/tests/utils.spec.ts | 3 + .../contract-as/tests/utils/helpers.ts | 16 + .../contract-as/tests/utils/spec.ts | 41 + smart-contracts/contract/Cargo.toml | 29 + smart-contracts/contract/README.md | 14 + .../contract/src/contract_api/account.rs | 86 + .../contract/src/contract_api/mod.rs | 42 + .../contract/src/contract_api/runtime.rs | 336 ++ .../contract/src/contract_api/storage.rs | 349 ++ .../contract/src/contract_api/system.rs | 154 + smart-contracts/contract/src/ext_ffi.rs | 638 +++ smart-contracts/contract/src/handlers.rs | 26 + smart-contracts/contract/src/lib.rs | 78 + .../contract/src/unwrap_or_revert.rs | 37 + .../contract/tests/version_numbers.rs | 4 + smart-contracts/contracts-as/.gitignore | 99 + .../client/bonding/assembly/index.ts | 49 + .../client/bonding/assembly/tsconfig.json | 6 + .../contracts-as/client/bonding/index.js | 12 + .../contracts-as/client/bonding/package.json | 9 + .../named-purse-payment/assembly/index.ts | 88 + .../assembly/tsconfig.json | 6 + .../client/named-purse-payment/index.js | 12 + .../client/named-purse-payment/package.json | 9 + .../client/revert/assembly/index.ts | 5 + .../client/revert/assembly/tsconfig.json | 6 + .../contracts-as/client/revert/index.js | 12 + .../contracts-as/client/revert/package.json | 9 + .../assembly/index.ts | 27 + .../assembly/tsconfig.json | 6 + .../client/transfer-to-account-u512/index.js | 12 + .../transfer-to-account-u512/package.json | 9 + .../client/unbonding/assembly/index.ts | 30 + .../client/unbonding/assembly/tsconfig.json | 6 + .../contracts-as/client/unbonding/index.js | 12 + .../client/unbonding/package.json | 9 + .../assembly/index.ts | 32 + .../assembly/tsconfig.json | 6 + .../test/add-update-associated-key/index.js | 12 + .../add-update-associated-key/package.json | 9 + .../test/authorized-keys/assembly/index.ts | 46 + .../authorized-keys/assembly/tsconfig.json | 6 + .../test/authorized-keys/index.js | 12 + .../test/authorized-keys/package.json | 9 + .../test/create-purse-01/assembly/index.ts | 30 + .../create-purse-01/assembly/tsconfig.json | 6 + .../test/create-purse-01/index.js | 12 + .../test/create-purse-01/package.json | 9 + .../assembly/index.ts | 26 + .../assembly/tsconfig.json | 6 + .../test/do-nothing-stored-caller/index.js | 12 + .../do-nothing-stored-caller/package.json | 9 + .../assembly/index.ts | 55 + .../assembly/tsconfig.json | 6 + .../test/do-nothing-stored-upgrader/index.js | 12 + .../do-nothing-stored-upgrader/package.json | 9 + .../test/do-nothing-stored/assembly/index.ts | 36 + .../do-nothing-stored/assembly/tsconfig.json | 6 + .../test/do-nothing-stored/index.js | 12 + .../test/do-nothing-stored/package.json | 9 + .../test/do-nothing/assembly/index.ts | 5 + .../test/do-nothing/assembly/tsconfig.json | 6 + .../contracts-as/test/do-nothing/index.js | 12 + .../contracts-as/test/do-nothing/package.json | 9 + .../test/endless-loop/assembly/index.ts | 7 + .../test/endless-loop/assembly/tsconfig.json | 6 + .../contracts-as/test/endless-loop/index.js | 12 + .../test/endless-loop/package.json | 9 + .../test/get-arg/assembly/index.ts | 36 + .../test/get-arg/assembly/tsconfig.json | 6 + .../contracts-as/test/get-arg/index.js | 12 + .../contracts-as/test/get-arg/package.json | 9 + .../test/get-blocktime/assembly/index.ts | 17 + .../test/get-blocktime/assembly/tsconfig.json | 6 + .../contracts-as/test/get-blocktime/index.js | 12 + .../test/get-blocktime/package.json | 9 + .../test/get-caller/assembly/index.ts | 19 + .../test/get-caller/assembly/tsconfig.json | 6 + .../contracts-as/test/get-caller/index.js | 12 + .../contracts-as/test/get-caller/package.json | 9 + .../test/get-phase-payment/assembly/index.ts | 52 + .../get-phase-payment/assembly/tsconfig.json | 6 + .../test/get-phase-payment/index.js | 12 + .../test/get-phase-payment/package.json | 9 + .../test/get-phase/assembly/index.ts | 17 + .../test/get-phase/assembly/tsconfig.json | 6 + .../contracts-as/test/get-phase/index.js | 12 + .../contracts-as/test/get-phase/package.json | 9 + .../test/groups/assembly/index.ts | 241 + .../test/groups/assembly/tsconfig.json | 6 + .../contracts-as/test/groups/index.js | 12 + .../contracts-as/test/groups/package.json | 9 + .../assembly/index.ts | 135 + .../assembly/tsconfig.json | 6 + .../test/key-management-thresholds/index.js | 12 + .../key-management-thresholds/package.json | 9 + .../test/list-named-keys/assembly/index.ts | 94 + .../list-named-keys/assembly/tsconfig.json | 6 + .../test/list-named-keys/index.js | 12 + .../test/list-named-keys/package.json | 9 + .../test/main-purse/assembly/index.ts | 27 + .../test/main-purse/assembly/tsconfig.json | 6 + .../contracts-as/test/main-purse/index.js | 12 + .../contracts-as/test/main-purse/package.json | 9 + .../test/manage-groups/assembly/index.ts | 189 + .../test/manage-groups/assembly/tsconfig.json | 6 + .../contracts-as/test/manage-groups/index.js | 12 + .../test/manage-groups/package.json | 9 + .../test/named-keys/assembly/index.ts | 181 + .../test/named-keys/assembly/tsconfig.json | 6 + .../contracts-as/test/named-keys/index.js | 12 + .../contracts-as/test/named-keys/package.json | 9 + .../overwrite-uref-content/assembly/index.ts | 34 + .../assembly/tsconfig.json | 6 + .../test/overwrite-uref-content/index.js | 12 + .../test/overwrite-uref-content/package.json | 9 + .../assembly/index.ts | 57 + .../assembly/tsconfig.json | 6 + .../test/purse-holder-stored-caller/index.js | 12 + .../purse-holder-stored-caller/package.json | 9 + .../assembly/index.ts | 114 + .../assembly/tsconfig.json | 6 + .../purse-holder-stored-upgrader/index.js | 12 + .../purse-holder-stored-upgrader/package.json | 9 + .../purse-holder-stored/assembly/index.ts | 71 + .../assembly/tsconfig.json | 6 + .../test/purse-holder-stored/index.js | 12 + .../test/purse-holder-stored/package.json | 9 + .../remove-associated-key/assembly/index.ts | 23 + .../assembly/tsconfig.json | 6 + .../test/remove-associated-key/index.js | 12 + .../test/remove-associated-key/package.json | 9 + .../assembly/index.ts | 45 + .../assembly/tsconfig.json | 6 + .../transfer-main-purse-to-new-purse/index.js | 12 + .../package.json | 9 + .../assembly/index.ts | 49 + .../assembly/tsconfig.json | 6 + .../transfer-purse-to-account-stored/index.js | 12 + .../package.json | 9 + .../assembly/index.ts | 62 + .../assembly/tsconfig.json | 6 + .../test/transfer-purse-to-account/index.js | 12 + .../transfer-purse-to-account/package.json | 9 + .../transfer-purse-to-purse/assembly/index.ts | 126 + .../assembly/tsconfig.json | 6 + .../test/transfer-purse-to-purse/index.js | 12 + .../test/transfer-purse-to-purse/package.json | 9 + smart-contracts/contracts/.cargo/config | 2 + .../SRE/create-test-node-01/Cargo.toml | 15 + .../SRE/create-test-node-01/src/main.rs | 10 + .../SRE/create-test-node-02/Cargo.toml | 15 + .../SRE/create-test-node-02/src/main.rs | 10 + .../SRE/create-test-node-03/Cargo.toml | 15 + .../SRE/create-test-node-03/src/main.rs | 10 + .../SRE/create-test-node-shared/Cargo.toml | 13 + .../SRE/create-test-node-shared/src/lib.rs | 43 + .../bench/create-accounts/Cargo.toml | 19 + .../bench/create-accounts/src/main.rs | 25 + .../contracts/bench/create-purses/Cargo.toml | 19 + .../contracts/bench/create-purses/src/main.rs | 31 + .../transfer-to-existing-account/Cargo.toml | 19 + .../transfer-to-existing-account/src/main.rs | 31 + .../bench/transfer-to-purse/Cargo.toml | 19 + .../bench/transfer-to-purse/src/main.rs | 21 + .../contracts/client/bonding/Cargo.toml | 18 + .../contracts/client/bonding/src/main.rs | 45 + .../client/counter-define/Cargo.toml | 19 + .../client/counter-define/src/main.rs | 168 + .../client/named-purse-payment/Cargo.toml | 19 + .../client/named-purse-payment/src/main.rs | 57 + .../contracts/client/revert/Cargo.toml | 18 + .../contracts/client/revert/src/main.rs | 10 + .../transfer-to-account-stored/Cargo.toml | 20 + .../transfer-to-account-stored/src/main.rs | 53 + .../Cargo.toml | 20 + .../src/main.rs | 62 + .../transfer-to-account-u512/Cargo.toml | 19 + .../transfer-to-account-u512/src/bin/main.rs | 7 + .../transfer-to-account-u512/src/lib.rs | 19 + .../client/transfer-to-account/Cargo.toml | 19 + .../transfer-to-account/src/bin/main.rs | 7 + .../client/transfer-to-account/src/lib.rs | 19 + .../contracts/client/unbonding/Cargo.toml | 18 + .../contracts/client/unbonding/src/main.rs | 27 + .../explorer/faucet-stored/Cargo.toml | 20 + .../explorer/faucet-stored/src/main.rs | 59 + .../contracts/explorer/faucet/Cargo.toml | 19 + .../contracts/explorer/faucet/src/bin/main.rs | 7 + .../contracts/explorer/faucet/src/lib.rs | 38 + .../integration/add-associated-key/Cargo.toml | 19 + .../add-associated-key/src/main.rs | 34 + .../integration/args-multi/Cargo.toml | 19 + .../integration/args-multi/src/main.rs | 19 + .../contracts/integration/args-u32/Cargo.toml | 19 + .../integration/args-u32/src/main.rs | 12 + .../integration/args-u512/Cargo.toml | 19 + .../integration/args-u512/src/main.rs | 15 + .../integration/create-named-purse/Cargo.toml | 19 + .../create-named-purse/src/main.rs | 28 + .../integration/direct-revert/Cargo.toml | 16 + .../integration/direct-revert/src/main.rs | 11 + .../integration/get-caller-call/Cargo.toml | 16 + .../integration/get-caller-call/src/main.rs | 21 + .../integration/get-caller-define/Cargo.toml | 16 + .../integration/get-caller-define/src/main.rs | 38 + .../list-known-urefs-call/Cargo.toml | 16 + .../list-known-urefs-call/src/main.rs | 22 + .../list-known-urefs-define/Cargo.toml | 16 + .../list-known-urefs-define/src/main.rs | 51 + .../payment-from-named-purse/Cargo.toml | 19 + .../payment-from-named-purse/src/main.rs | 62 + .../integration/set-key-thresholds/Cargo.toml | 19 + .../set-key-thresholds/src/main.rs | 44 + .../subcall-revert-call/Cargo.toml | 16 + .../subcall-revert-call/src/main.rs | 21 + .../subcall-revert-define/Cargo.toml | 16 + .../subcall-revert-define/src/main.rs | 49 + .../update-associated-key/Cargo.toml | 19 + .../update-associated-key/src/main.rs | 35 + .../integration/write-all-types/Cargo.toml | 16 + .../integration/write-all-types/src/main.rs | 99 + .../host-function-metrics/Cargo.toml | 20 + .../host-function-metrics/src/lib.rs | 676 +++ .../profiling/simple-transfer/Cargo.toml | 19 + .../profiling/simple-transfer/src/main.rs | 28 + .../profiling/state-initializer/Cargo.toml | 19 + .../profiling/state-initializer/src/main.rs | 36 + .../contracts/system/mint-install/Cargo.toml | 20 + .../contracts/system/mint-install/src/main.rs | 48 + .../contracts/system/mint-token/Cargo.toml | 16 + .../system/mint-token/src/bin/main.rs | 22 + .../contracts/system/mint-token/src/lib.rs | 160 + .../contracts/system/pos-install/Cargo.toml | 22 + .../contracts/system/pos-install/src/main.rs | 190 + .../contracts/system/pos/Cargo.toml | 14 + .../contracts/system/pos/src/bin/main.rs | 32 + .../contracts/system/pos/src/lib.rs | 228 + .../standard-payment-install/Cargo.toml | 21 + .../standard-payment-install/src/main.rs | 58 + .../system/standard-payment/Cargo.toml | 25 + .../system/standard-payment/src/bin/main.rs | 7 + .../system/standard-payment/src/lib.rs | 51 + .../system/test-mint-token/Cargo.toml | 19 + .../system/test-mint-token/src/main.rs | 64 + .../contracts/test/add-gas-subcall/Cargo.toml | 19 + .../test/add-gas-subcall/src/main.rs | 77 + .../test/add-update-associated-key/Cargo.toml | 19 + .../add-update-associated-key/src/main.rs | 24 + .../contracts/test/authorized-keys/Cargo.toml | 19 + .../test/authorized-keys/src/main.rs | 35 + .../test/contract-context/Cargo.toml | 19 + .../test/contract-context/src/main.rs | 174 + .../contracts/test/create-purse-01/Cargo.toml | 19 + .../test/create-purse-01/src/bin/main.rs | 7 + .../contracts/test/create-purse-01/src/lib.rs | 15 + .../test/deserialize-error/Cargo.toml | 19 + .../test/deserialize-error/src/main.rs | 106 + .../test/do-nothing-stored-caller/Cargo.toml | 19 + .../test/do-nothing-stored-caller/src/main.rs | 34 + .../do-nothing-stored-upgrader/Cargo.toml | 20 + .../do-nothing-stored-upgrader/src/main.rs | 61 + .../test/do-nothing-stored/Cargo.toml | 19 + .../test/do-nothing-stored/src/main.rs | 47 + .../contracts/test/do-nothing/Cargo.toml | 18 + .../contracts/test/do-nothing/src/main.rs | 11 + .../test/ee-221-regression/Cargo.toml | 19 + .../test/ee-221-regression/src/main.rs | 18 + .../test/ee-401-regression-call/Cargo.toml | 19 + .../test/ee-401-regression-call/src/main.rs | 26 + .../test/ee-401-regression/Cargo.toml | 19 + .../test/ee-401-regression/src/main.rs | 51 + .../test/ee-441-rng-state/Cargo.toml | 19 + .../test/ee-441-rng-state/src/main.rs | 90 + .../test/ee-460-regression/Cargo.toml | 19 + .../test/ee-460-regression/src/main.rs | 15 + .../test/ee-532-regression/Cargo.toml | 18 + .../test/ee-532-regression/src/main.rs | 11 + .../test/ee-536-regression/Cargo.toml | 19 + .../test/ee-536-regression/src/main.rs | 55 + .../test/ee-539-regression/Cargo.toml | 19 + .../test/ee-539-regression/src/main.rs | 22 + .../test/ee-549-regression/Cargo.toml | 20 + .../test/ee-549-regression/src/main.rs | 23 + .../test/ee-550-regression/Cargo.toml | 19 + .../test/ee-550-regression/src/main.rs | 75 + .../test/ee-572-regression-create/Cargo.toml | 19 + .../test/ee-572-regression-create/src/main.rs | 47 + .../ee-572-regression-escalate/Cargo.toml | 19 + .../ee-572-regression-escalate/src/main.rs | 17 + .../test/ee-584-regression/Cargo.toml | 19 + .../test/ee-584-regression/src/main.rs | 15 + .../test/ee-597-regression/Cargo.toml | 19 + .../test/ee-597-regression/src/main.rs | 28 + .../test/ee-598-regression/Cargo.toml | 19 + .../test/ee-598-regression/src/main.rs | 34 + .../test/ee-599-regression/Cargo.toml | 19 + .../test/ee-599-regression/src/main.rs | 198 + .../test/ee-601-regression/Cargo.toml | 19 + .../test/ee-601-regression/src/main.rs | 52 + .../test/ee-771-regression/Cargo.toml | 19 + .../test/ee-771-regression/src/main.rs | 98 + .../test/ee-803-regression/Cargo.toml | 19 + .../test/ee-803-regression/src/main.rs | 48 + .../contracts/test/endless-loop/Cargo.toml | 18 + .../contracts/test/endless-loop/src/main.rs | 11 + .../test/expensive-calculation/Cargo.toml | 19 + .../test/expensive-calculation/src/main.rs | 50 + .../contracts/test/get-arg/Cargo.toml | 19 + .../contracts/test/get-arg/src/main.rs | 21 + .../contracts/test/get-blocktime/Cargo.toml | 19 + .../contracts/test/get-blocktime/src/main.rs | 19 + .../test/get-caller-subcall/Cargo.toml | 19 + .../test/get-caller-subcall/src/main.rs | 65 + .../contracts/test/get-caller/Cargo.toml | 19 + .../contracts/test/get-caller/src/main.rs | 17 + .../test/get-phase-payment/Cargo.toml | 19 + .../test/get-phase-payment/src/main.rs | 34 + .../contracts/test/get-phase/Cargo.toml | 19 + .../contracts/test/get-phase/src/main.rs | 17 + .../contracts/test/groups/Cargo.toml | 19 + .../contracts/test/groups/src/main.rs | 235 + .../test/key-management-thresholds/Cargo.toml | 19 + .../key-management-thresholds/src/main.rs | 73 + .../contracts/test/list-named-keys/Cargo.toml | 19 + .../test/list-named-keys/src/main.rs | 41 + .../contracts/test/local-state-add/Cargo.toml | 19 + .../test/local-state-add/src/main.rs | 29 + .../test/local-state-stored-caller/Cargo.toml | 19 + .../local-state-stored-caller/src/main.rs | 15 + .../local-state-stored-upgraded/Cargo.toml | 20 + .../src/bin/main.rs | 7 + .../local-state-stored-upgraded/src/lib.rs | 45 + .../local-state-stored-upgrader/Cargo.toml | 20 + .../src/bin/main.rs | 55 + .../test/local-state-stored/Cargo.toml | 20 + .../test/local-state-stored/src/main.rs | 54 + .../contracts/test/local-state/Cargo.toml | 18 + .../test/local-state/src/bin/main.rs | 7 + .../contracts/test/local-state/src/lib.rs | 33 + .../contracts/test/main-purse/Cargo.toml | 19 + .../contracts/test/main-purse/src/main.rs | 17 + .../contracts/test/manage-groups/Cargo.toml | 19 + .../contracts/test/manage-groups/src/main.rs | 166 + .../test/measure-gas-subcall/Cargo.toml | 19 + .../test/measure-gas-subcall/src/main.rs | 94 + .../contracts/test/mint-purse/Cargo.toml | 19 + .../contracts/test/mint-purse/src/main.rs | 52 + .../test/modified-mint-caller/Cargo.toml | 19 + .../test/modified-mint-caller/src/main.rs | 20 + .../test/modified-mint-upgrader/Cargo.toml | 20 + .../test/modified-mint-upgrader/src/main.rs | 66 + .../contracts/test/modified-mint/Cargo.toml | 20 + .../test/modified-mint/src/bin/main.rs | 22 + .../contracts/test/modified-mint/src/lib.rs | 160 + .../test/modified-system-upgrader/Cargo.toml | 22 + .../test/modified-system-upgrader/src/main.rs | 262 + .../contracts/test/named-keys/Cargo.toml | 19 + .../contracts/test/named-keys/src/main.rs | 90 + .../test/overwrite-uref-content/Cargo.toml | 19 + .../test/overwrite-uref-content/src/main.rs | 28 + .../contracts/test/pos-bonding/Cargo.toml | 19 + .../contracts/test/pos-bonding/src/main.rs | 88 + .../test/pos-finalize-payment/Cargo.toml | 19 + .../test/pos-finalize-payment/src/main.rs | 67 + .../test/pos-get-payment-purse/Cargo.toml | 19 + .../test/pos-get-payment-purse/src/main.rs | 50 + .../test/pos-refund-purse/Cargo.toml | 19 + .../test/pos-refund-purse/src/main.rs | 86 + .../purse-holder-stored-caller/Cargo.toml | 19 + .../purse-holder-stored-caller/src/main.rs | 38 + .../purse-holder-stored-upgrader/Cargo.toml | 19 + .../purse-holder-stored-upgrader/src/main.rs | 99 + .../test/purse-holder-stored/Cargo.toml | 19 + .../test/purse-holder-stored/src/main.rs | 76 + .../test/remove-associated-key/Cargo.toml | 19 + .../test/remove-associated-key/src/main.rs | 16 + .../test/test-payment-stored/Cargo.toml | 20 + .../test/test-payment-stored/src/main.rs | 48 + .../Cargo.toml | 19 + .../src/main.rs | 26 + .../Cargo.toml | 19 + .../src/main.rs | 59 + .../Cargo.toml | 20 + .../src/main.rs | 56 + .../test/transfer-purse-to-account/Cargo.toml | 19 + .../transfer-purse-to-account/src/bin/main.rs | 7 + .../test/transfer-purse-to-account/src/lib.rs | 36 + .../test/transfer-purse-to-purse/Cargo.toml | 19 + .../test/transfer-purse-to-purse/src/main.rs | 80 + types/Cargo.toml | 36 + types/README.md | 14 + types/benches/bytesrepr_bench.rs | 509 ++ types/src/access_rights.rs | 146 + types/src/account.rs | 406 ++ types/src/api_error.rs | 833 +++ types/src/block_time.rs | 46 + types/src/bytesrepr.rs | 1089 ++++ types/src/cl_type.rs | 685 +++ types/src/cl_value.rs | 132 + types/src/contract_wasm.rs | 87 + types/src/contracts.rs | 1052 ++++ types/src/gens.rs | 329 ++ types/src/key.rs | 343 ++ types/src/lib.rs | 75 + types/src/mint.rs | 72 + types/src/mint/runtime_provider.rs | 10 + types/src/mint/storage_provider.rs | 29 + types/src/phase.rs | 55 + types/src/proof_of_stake.rs | 426 ++ types/src/proof_of_stake/mint_provider.rs | 23 + types/src/proof_of_stake/queue.rs | 205 + types/src/proof_of_stake/queue_provider.rs | 16 + types/src/proof_of_stake/runtime_provider.rs | 22 + types/src/proof_of_stake/stakes.rs | 288 ++ types/src/proof_of_stake/stakes_provider.rs | 10 + types/src/protocol_version.rs | 411 ++ types/src/runtime_args.rs | 237 + types/src/semver.rs | 133 + types/src/standard_payment.rs | 24 + .../src/standard_payment/account_provider.rs | 7 + types/src/standard_payment/mint_provider.rs | 12 + .../proof_of_stake_provider.rs | 7 + types/src/system_contract_errors/mint.rs | 131 + types/src/system_contract_errors/mod.rs | 28 + types/src/system_contract_errors/pos.rs | 160 + types/src/system_contract_type.rs | 120 + types/src/transfer_result.rs | 39 + types/src/uint.rs | 789 +++ types/src/uref.rs | 171 + types/tests/version_numbers.rs | 4 + 806 files changed, 83733 insertions(+), 314 deletions(-) create mode 100644 Makefile create mode 100644 grpc/server/.rpm/casperlabs-engine-grpc-server.spec create mode 100644 grpc/server/Cargo.toml create mode 100644 grpc/server/README.md create mode 100644 grpc/server/build.rs create mode 100644 grpc/server/debian/postinst create mode 100644 grpc/server/packaging/casperlabs-engine-grpc-server.service create mode 100644 grpc/server/protobuf/google/api/annotations.proto create mode 100644 grpc/server/protobuf/google/api/http.proto create mode 100644 grpc/server/protobuf/google/protobuf/descriptor.proto create mode 100644 grpc/server/protobuf/google/protobuf/empty.proto create mode 100644 grpc/server/protobuf/io/casperlabs/casper/consensus/consensus.proto create mode 100644 grpc/server/protobuf/io/casperlabs/casper/consensus/info.proto create mode 100644 grpc/server/protobuf/io/casperlabs/casper/consensus/state.proto create mode 100644 grpc/server/protobuf/io/casperlabs/comm/discovery/kademlia.proto create mode 100644 grpc/server/protobuf/io/casperlabs/comm/discovery/node.proto create mode 100644 grpc/server/protobuf/io/casperlabs/comm/gossiping/gossiping.proto create mode 100644 grpc/server/protobuf/io/casperlabs/ipc/ipc.proto create mode 100644 grpc/server/protobuf/io/casperlabs/ipc/transforms.proto create mode 100644 grpc/server/protobuf/io/casperlabs/node/api/casper.proto create mode 100644 grpc/server/protobuf/io/casperlabs/node/api/control.proto create mode 100644 grpc/server/protobuf/io/casperlabs/node/api/diagnostics.proto create mode 100644 grpc/server/protobuf/io/casperlabs/storage/storage.proto create mode 100644 grpc/server/src/engine_server/mappings/ipc/bond.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/deploy_item.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/deploy_result.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/exec_config.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/executable_deploy_item.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/execute_request.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/execution_effect.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/genesis_account.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/genesis_config.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/mod.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/query_request.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/run_genesis_request.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/upgrade_request.rs create mode 100644 grpc/server/src/engine_server/mappings/ipc/wasm_costs.rs create mode 100644 grpc/server/src/engine_server/mappings/mod.rs create mode 100644 grpc/server/src/engine_server/mappings/state/account.rs create mode 100644 grpc/server/src/engine_server/mappings/state/big_int.rs create mode 100644 grpc/server/src/engine_server/mappings/state/cl_type.rs create mode 100644 grpc/server/src/engine_server/mappings/state/cl_value.rs create mode 100644 grpc/server/src/engine_server/mappings/state/contract.rs create mode 100644 grpc/server/src/engine_server/mappings/state/contract_package.rs create mode 100644 grpc/server/src/engine_server/mappings/state/contract_wasm.rs create mode 100644 grpc/server/src/engine_server/mappings/state/key.rs create mode 100644 grpc/server/src/engine_server/mappings/state/mod.rs create mode 100644 grpc/server/src/engine_server/mappings/state/named_key.rs create mode 100644 grpc/server/src/engine_server/mappings/state/protocol_version.rs create mode 100644 grpc/server/src/engine_server/mappings/state/semver.rs create mode 100644 grpc/server/src/engine_server/mappings/state/stored_value.rs create mode 100644 grpc/server/src/engine_server/mappings/state/uref.rs create mode 100644 grpc/server/src/engine_server/mappings/transforms/error.rs create mode 100644 grpc/server/src/engine_server/mappings/transforms/mod.rs create mode 100644 grpc/server/src/engine_server/mappings/transforms/transform.rs create mode 100644 grpc/server/src/engine_server/mappings/transforms/transform_entry.rs create mode 100644 grpc/server/src/engine_server/mappings/transforms/transform_map.rs create mode 100644 grpc/server/src/engine_server/mod.rs create mode 100644 grpc/server/src/lib.rs create mode 100644 grpc/server/src/main.rs create mode 100644 grpc/test-support/Cargo.toml create mode 100644 grpc/test-support/README.md create mode 100644 grpc/test-support/src/account.rs create mode 100644 grpc/test-support/src/code.rs create mode 100644 grpc/test-support/src/error.rs create mode 100644 grpc/test-support/src/internal/additive_map_diff.rs create mode 100644 grpc/test-support/src/internal/deploy_item_builder.rs create mode 100644 grpc/test-support/src/internal/exec_with_return.rs create mode 100644 grpc/test-support/src/internal/execute_request_builder.rs create mode 100644 grpc/test-support/src/internal/mod.rs create mode 100644 grpc/test-support/src/internal/upgrade_request_builder.rs create mode 100644 grpc/test-support/src/internal/utils.rs create mode 100644 grpc/test-support/src/internal/wasm_test_builder.rs create mode 100644 grpc/test-support/src/lib.rs create mode 100644 grpc/test-support/src/session.rs create mode 100644 grpc/test-support/src/test_context.rs create mode 100644 grpc/test-support/src/value.rs create mode 100644 grpc/test-support/tests/version_numbers.rs create mode 100644 grpc/tests/Cargo.toml create mode 100644 grpc/tests/benches/transfer_bench.rs create mode 100644 grpc/tests/src/lib.rs create mode 100644 grpc/tests/src/logging/metrics.rs create mode 100644 grpc/tests/src/profiling/README.md create mode 100644 grpc/tests/src/profiling/concurrent_executor.rs create mode 100755 grpc/tests/src/profiling/concurrent_executor.sh create mode 100644 grpc/tests/src/profiling/host_function_metrics.rs create mode 100644 grpc/tests/src/profiling/mod.rs create mode 100755 grpc/tests/src/profiling/perf.sh create mode 100644 grpc/tests/src/profiling/simple_transfer.rs create mode 100644 grpc/tests/src/profiling/state_initializer.rs create mode 100644 grpc/tests/src/test/check_transfer_success.rs create mode 100644 grpc/tests/src/test/contract_api/account/associated_keys.rs create mode 100644 grpc/tests/src/test/contract_api/account/authorized_keys.rs create mode 100644 grpc/tests/src/test/contract_api/account/key_management_thresholds.rs create mode 100644 grpc/tests/src/test/contract_api/account/mod.rs create mode 100644 grpc/tests/src/test/contract_api/account/named_keys.rs create mode 100644 grpc/tests/src/test/contract_api/create_purse.rs create mode 100644 grpc/tests/src/test/contract_api/get_arg.rs create mode 100644 grpc/tests/src/test/contract_api/get_blocktime.rs create mode 100644 grpc/tests/src/test/contract_api/get_caller.rs create mode 100644 grpc/tests/src/test/contract_api/get_phase.rs create mode 100644 grpc/tests/src/test/contract_api/list_named_keys.rs create mode 100644 grpc/tests/src/test/contract_api/main_purse.rs create mode 100644 grpc/tests/src/test/contract_api/mint_purse.rs create mode 100644 grpc/tests/src/test/contract_api/mod.rs create mode 100644 grpc/tests/src/test/contract_api/revert.rs create mode 100644 grpc/tests/src/test/contract_api/subcall.rs create mode 100644 grpc/tests/src/test/contract_api/transfer.rs create mode 100644 grpc/tests/src/test/contract_api/transfer_purse_to_account.rs create mode 100644 grpc/tests/src/test/contract_api/transfer_purse_to_purse.rs create mode 100644 grpc/tests/src/test/contract_api/transfer_stored.rs create mode 100644 grpc/tests/src/test/contract_api/transfer_u512_stored.rs create mode 100644 grpc/tests/src/test/contract_context.rs create mode 100644 grpc/tests/src/test/contract_headers.rs create mode 100644 grpc/tests/src/test/counter.rs create mode 100644 grpc/tests/src/test/deploy/mod.rs create mode 100644 grpc/tests/src/test/deploy/non_standard_payment.rs create mode 100644 grpc/tests/src/test/deploy/preconditions.rs create mode 100644 grpc/tests/src/test/deploy/stored_contracts.rs create mode 100644 grpc/tests/src/test/explorer/faucet.rs create mode 100644 grpc/tests/src/test/explorer/faucet_stored.rs create mode 100644 grpc/tests/src/test/explorer/mod.rs create mode 100644 grpc/tests/src/test/groups.rs create mode 100644 grpc/tests/src/test/manage_groups.rs create mode 100644 grpc/tests/src/test/mod.rs create mode 100644 grpc/tests/src/test/regression/ee_221.rs create mode 100644 grpc/tests/src/test/regression/ee_401.rs create mode 100644 grpc/tests/src/test/regression/ee_441.rs create mode 100644 grpc/tests/src/test/regression/ee_460.rs create mode 100644 grpc/tests/src/test/regression/ee_468.rs create mode 100644 grpc/tests/src/test/regression/ee_470.rs create mode 100644 grpc/tests/src/test/regression/ee_532.rs create mode 100644 grpc/tests/src/test/regression/ee_536.rs create mode 100644 grpc/tests/src/test/regression/ee_539.rs create mode 100644 grpc/tests/src/test/regression/ee_549.rs create mode 100644 grpc/tests/src/test/regression/ee_550.rs create mode 100644 grpc/tests/src/test/regression/ee_572.rs create mode 100644 grpc/tests/src/test/regression/ee_584.rs create mode 100644 grpc/tests/src/test/regression/ee_597.rs create mode 100644 grpc/tests/src/test/regression/ee_598.rs create mode 100644 grpc/tests/src/test/regression/ee_599.rs create mode 100644 grpc/tests/src/test/regression/ee_601.rs create mode 100644 grpc/tests/src/test/regression/ee_771.rs create mode 100644 grpc/tests/src/test/regression/ee_803.rs create mode 100644 grpc/tests/src/test/regression/ee_890.rs create mode 100644 grpc/tests/src/test/regression/mod.rs create mode 100644 grpc/tests/src/test/system_contracts/genesis.rs create mode 100644 grpc/tests/src/test/system_contracts/mint_install.rs create mode 100644 grpc/tests/src/test/system_contracts/mod.rs create mode 100644 grpc/tests/src/test/system_contracts/pos_install.rs create mode 100644 grpc/tests/src/test/system_contracts/proof_of_stake/bonding.rs create mode 100644 grpc/tests/src/test/system_contracts/proof_of_stake/commit_validators.rs create mode 100644 grpc/tests/src/test/system_contracts/proof_of_stake/finalize_payment.rs create mode 100644 grpc/tests/src/test/system_contracts/proof_of_stake/get_payment_purse.rs create mode 100644 grpc/tests/src/test/system_contracts/proof_of_stake/mod.rs create mode 100644 grpc/tests/src/test/system_contracts/proof_of_stake/refund_purse.rs create mode 100644 grpc/tests/src/test/system_contracts/standard_payment.rs create mode 100644 grpc/tests/src/test/system_contracts/standard_payment_install.rs create mode 100644 grpc/tests/src/test/system_contracts/upgrade.rs create mode 100644 grpc/tests/src/test/upgrade.rs create mode 100644 grpc/tests/src/test/wasmless_transfer.rs create mode 100644 node/Cargo.toml create mode 100644 node/benches/trie_bench.rs rename {src => node/src}/apps/client/main.rs (100%) rename {src => node/src}/apps/node/cli.rs (100%) rename {src => node/src}/apps/node/config.rs (100%) rename {src => node/src/apps/node}/logging.rs (100%) rename {src => node/src}/apps/node/main.rs (100%) rename {src => node/src}/components.rs (98%) rename {src => node/src}/components/api_server.rs (100%) rename {src => node/src}/components/api_server/config.rs (100%) rename {src => node/src}/components/api_server/event.rs (100%) rename {src => node/src}/components/consensus.rs (100%) rename {src => node/src}/components/consensus/consensus_protocol.rs (100%) rename {src => node/src}/components/consensus/consensus_protocol/protocol_state.rs (100%) rename {src => node/src}/components/consensus/consensus_protocol/synchronizer.rs (100%) rename {src => node/src}/components/consensus/deploy_buffer.rs (100%) rename {src => node/src}/components/consensus/era_supervisor.rs (100%) rename {src => node/src}/components/consensus/highway_core.rs (100%) rename {src => node/src}/components/consensus/highway_core/active_validator.rs (100%) rename {src => node/src}/components/consensus/highway_core/block.rs (100%) rename {src => node/src}/components/consensus/highway_core/evidence.rs (100%) rename {src => node/src}/components/consensus/highway_core/finality_detector.rs (100%) rename {src => node/src}/components/consensus/highway_core/highway.rs (100%) rename {src => node/src}/components/consensus/highway_core/state.rs (100%) rename {src => node/src}/components/consensus/highway_core/tallies.rs (100%) rename {src => node/src}/components/consensus/highway_core/test_macros.rs (100%) rename {src => node/src}/components/consensus/highway_core/validators.rs (100%) rename {src => node/src}/components/consensus/highway_core/vertex.rs (100%) rename {src => node/src}/components/consensus/highway_core/vote.rs (100%) rename {src => node/src}/components/consensus/highway_testing.rs (100%) rename {src => node/src}/components/consensus/protocols.rs (100%) rename {src => node/src}/components/consensus/protocols/highway.rs (100%) rename {src => node/src}/components/consensus/traits.rs (100%) create mode 100644 node/src/components/contract_runtime.rs rename {src => node/src}/components/deploy_gossiper.rs (100%) rename {src => node/src}/components/in_memory_network.rs (100%) rename {src => node/src}/components/pinger.rs (100%) rename {src => node/src}/components/small_network.rs (100%) rename {src => node/src}/components/small_network/config.rs (100%) rename {src => node/src}/components/small_network/endpoint.rs (100%) rename {src => node/src}/components/small_network/error.rs (100%) rename {src => node/src}/components/small_network/event.rs (100%) rename {src => node/src}/components/small_network/message.rs (100%) rename {src => node/src}/components/small_network/test.rs (100%) rename {src => node/src}/components/storage.rs (100%) rename {src => node/src}/components/storage/config.rs (88%) rename {src => node/src}/components/storage/error.rs (100%) rename {src => node/src}/components/storage/in_mem_store.rs (100%) rename {src => node/src}/components/storage/lmdb_store.rs (100%) rename {src => node/src}/components/storage/store.rs (100%) create mode 100644 node/src/contract_core.rs create mode 100644 node/src/contract_core/engine_state/deploy_item.rs create mode 100644 node/src/contract_core/engine_state/engine_config.rs create mode 100644 node/src/contract_core/engine_state/error.rs create mode 100644 node/src/contract_core/engine_state/executable_deploy_item.rs create mode 100644 node/src/contract_core/engine_state/execute_request.rs create mode 100644 node/src/contract_core/engine_state/execution_effect.rs create mode 100644 node/src/contract_core/engine_state/execution_result.rs create mode 100644 node/src/contract_core/engine_state/genesis.rs create mode 100644 node/src/contract_core/engine_state/mod.rs create mode 100644 node/src/contract_core/engine_state/op.rs create mode 100644 node/src/contract_core/engine_state/query.rs create mode 100644 node/src/contract_core/engine_state/run_genesis_request.rs create mode 100644 node/src/contract_core/engine_state/system_contract_cache.rs create mode 100644 node/src/contract_core/engine_state/transfer.rs create mode 100644 node/src/contract_core/engine_state/upgrade.rs create mode 100644 node/src/contract_core/engine_state/utils.rs create mode 100644 node/src/contract_core/execution/address_generator.rs create mode 100644 node/src/contract_core/execution/error.rs create mode 100644 node/src/contract_core/execution/executor.rs create mode 100644 node/src/contract_core/execution/mod.rs create mode 100644 node/src/contract_core/execution/tests.rs create mode 100644 node/src/contract_core/resolvers/error.rs create mode 100644 node/src/contract_core/resolvers/memory_resolver.rs create mode 100644 node/src/contract_core/resolvers/mod.rs create mode 100644 node/src/contract_core/resolvers/v1_function_index.rs create mode 100644 node/src/contract_core/resolvers/v1_resolver.rs create mode 100644 node/src/contract_core/runtime/args.rs create mode 100644 node/src/contract_core/runtime/externals.rs create mode 100644 node/src/contract_core/runtime/mint_internal.rs create mode 100644 node/src/contract_core/runtime/mod.rs create mode 100644 node/src/contract_core/runtime/proof_of_stake_internal.rs create mode 100644 node/src/contract_core/runtime/scoped_instrumenter.rs create mode 100644 node/src/contract_core/runtime/standard_payment_internal.rs create mode 100644 node/src/contract_core/runtime_context/mod.rs create mode 100644 node/src/contract_core/runtime_context/tests.rs create mode 100644 node/src/contract_core/tracking_copy/byte_size.rs create mode 100644 node/src/contract_core/tracking_copy/ext.rs create mode 100644 node/src/contract_core/tracking_copy/meter.rs create mode 100644 node/src/contract_core/tracking_copy/mod.rs create mode 100644 node/src/contract_core/tracking_copy/tests.rs create mode 100644 node/src/contract_shared.rs create mode 100644 node/src/contract_shared/account.rs create mode 100644 node/src/contract_shared/account/action_thresholds.rs create mode 100644 node/src/contract_shared/account/associated_keys.rs create mode 100644 node/src/contract_shared/additive_map.rs create mode 100644 node/src/contract_shared/gas.rs create mode 100644 node/src/contract_shared/logging/README.md create mode 100644 node/src/contract_shared/logging/mod.rs create mode 100644 node/src/contract_shared/logging/settings.rs create mode 100644 node/src/contract_shared/logging/structured_message.rs create mode 100644 node/src/contract_shared/logging/terminal_logger.rs create mode 100644 node/src/contract_shared/motes.rs create mode 100644 node/src/contract_shared/newtypes/macros.rs create mode 100644 node/src/contract_shared/newtypes/mod.rs create mode 100644 node/src/contract_shared/page_size.rs create mode 100644 node/src/contract_shared/socket.rs create mode 100644 node/src/contract_shared/stored_value.rs create mode 100644 node/src/contract_shared/test_utils.rs create mode 100644 node/src/contract_shared/transform.rs create mode 100644 node/src/contract_shared/type_mismatch.rs create mode 100644 node/src/contract_shared/utils.rs create mode 100644 node/src/contract_shared/wasm.rs create mode 100644 node/src/contract_shared/wasm_costs.rs create mode 100644 node/src/contract_shared/wasm_prep.rs create mode 100644 node/src/contract_storage.rs create mode 100644 node/src/contract_storage/error/in_memory.rs create mode 100644 node/src/contract_storage/error/lmdb.rs create mode 100644 node/src/contract_storage/error/mod.rs create mode 100644 node/src/contract_storage/global_state/in_memory.rs create mode 100644 node/src/contract_storage/global_state/lmdb.rs create mode 100644 node/src/contract_storage/global_state/mod.rs create mode 100644 node/src/contract_storage/protocol_data.rs create mode 100644 node/src/contract_storage/protocol_data_store/in_memory.rs create mode 100644 node/src/contract_storage/protocol_data_store/lmdb.rs create mode 100644 node/src/contract_storage/protocol_data_store/mod.rs create mode 100644 node/src/contract_storage/protocol_data_store/tests/mod.rs create mode 100644 node/src/contract_storage/protocol_data_store/tests/proptests.rs create mode 100644 node/src/contract_storage/store/mod.rs create mode 100644 node/src/contract_storage/store/store_ext.rs create mode 100644 node/src/contract_storage/store/tests.rs create mode 100644 node/src/contract_storage/transaction_source/in_memory.rs create mode 100644 node/src/contract_storage/transaction_source/lmdb.rs create mode 100644 node/src/contract_storage/transaction_source/mod.rs create mode 100644 node/src/contract_storage/trie/gens.rs create mode 100644 node/src/contract_storage/trie/mod.rs create mode 100644 node/src/contract_storage/trie/tests.rs create mode 100644 node/src/contract_storage/trie_store/in_memory.rs create mode 100644 node/src/contract_storage/trie_store/lmdb.rs create mode 100644 node/src/contract_storage/trie_store/mod.rs create mode 100644 node/src/contract_storage/trie_store/operations/mod.rs create mode 100644 node/src/contract_storage/trie_store/operations/tests/ee_699.rs create mode 100644 node/src/contract_storage/trie_store/operations/tests/keys.rs create mode 100644 node/src/contract_storage/trie_store/operations/tests/mod.rs create mode 100644 node/src/contract_storage/trie_store/operations/tests/proptests.rs create mode 100644 node/src/contract_storage/trie_store/operations/tests/read.rs create mode 100644 node/src/contract_storage/trie_store/operations/tests/scan.rs create mode 100644 node/src/contract_storage/trie_store/operations/tests/write.rs create mode 100644 node/src/contract_storage/trie_store/tests/concurrent.rs create mode 100644 node/src/contract_storage/trie_store/tests/mod.rs create mode 100644 node/src/contract_storage/trie_store/tests/proptests.rs create mode 100644 node/src/contract_storage/trie_store/tests/simple.rs rename {src => node/src}/crypto.rs (100%) rename {src => node/src}/crypto/asymmetric_key.rs (100%) rename {src => node/src}/crypto/error.rs (100%) rename {src => node/src}/crypto/hash.rs (100%) rename {src => node/src}/effect.rs (100%) rename {src => node/src}/effect/announcements.rs (100%) rename {src => node/src}/effect/requests.rs (100%) rename {src => node/src}/lib.rs (95%) create mode 100644 node/src/logging.rs rename {src => node/src}/reactor.rs (100%) rename {src => node/src}/reactor/non_validator.rs (100%) rename {src => node/src}/reactor/queue_kind.rs (100%) rename {src => node/src}/reactor/validator.rs (94%) rename {src => node/src}/reactor/validator/config.rs (100%) rename {src => node/src}/reactor/validator/error.rs (100%) rename {src => node/src}/testing.rs (100%) rename {src => node/src}/testing/network.rs (100%) rename {src => node/src}/tls.rs (100%) rename {src => node/src}/types.rs (100%) rename {src => node/src}/types/block.rs (100%) rename {src => node/src}/types/deploy.rs (100%) rename {src => node/src}/utils.rs (100%) rename {src => node/src}/utils/gossip_table.rs (100%) rename {src => node/src}/utils/round_robin.rs (100%) create mode 100644 smart-contracts/contract-as/.gitignore create mode 100644 smart-contracts/contract-as/.npmignore create mode 100644 smart-contracts/contract-as/.npmrc create mode 100644 smart-contracts/contract-as/README.md create mode 100644 smart-contracts/contract-as/assembly/account.ts create mode 100644 smart-contracts/contract-as/assembly/bignum.ts create mode 100644 smart-contracts/contract-as/assembly/bytesrepr.ts create mode 100644 smart-contracts/contract-as/assembly/clvalue.ts create mode 100644 smart-contracts/contract-as/assembly/constants.ts create mode 100644 smart-contracts/contract-as/assembly/contracts.ts create mode 100644 smart-contracts/contract-as/assembly/error.ts create mode 100644 smart-contracts/contract-as/assembly/externals.ts create mode 100644 smart-contracts/contract-as/assembly/index.ts create mode 100644 smart-contracts/contract-as/assembly/key.ts create mode 100644 smart-contracts/contract-as/assembly/local.ts create mode 100644 smart-contracts/contract-as/assembly/option.ts create mode 100644 smart-contracts/contract-as/assembly/pair.ts create mode 100644 smart-contracts/contract-as/assembly/purse.ts create mode 100644 smart-contracts/contract-as/assembly/runtime_args.ts create mode 100644 smart-contracts/contract-as/assembly/tsconfig.json create mode 100644 smart-contracts/contract-as/assembly/unit.ts create mode 100644 smart-contracts/contract-as/assembly/uref.ts create mode 100644 smart-contracts/contract-as/assembly/utils.ts create mode 100644 smart-contracts/contract-as/build/.gitignore create mode 100644 smart-contracts/contract-as/index.js create mode 100644 smart-contracts/contract-as/package-lock.json create mode 100644 smart-contracts/contract-as/package.json create mode 100644 smart-contracts/contract-as/tests/assembly/bignum.spec.as.ts create mode 100644 smart-contracts/contract-as/tests/assembly/bytesrepr.spec.as.ts create mode 100644 smart-contracts/contract-as/tests/assembly/runtime_args.spec.as.ts create mode 100644 smart-contracts/contract-as/tests/assembly/utils.spec.as.ts create mode 100644 smart-contracts/contract-as/tests/bignum.spec.ts create mode 100644 smart-contracts/contract-as/tests/bytesrepr.spec.ts create mode 100644 smart-contracts/contract-as/tests/runtime_args.spec.ts create mode 100644 smart-contracts/contract-as/tests/tsconfig.json create mode 100644 smart-contracts/contract-as/tests/utils.spec.ts create mode 100644 smart-contracts/contract-as/tests/utils/helpers.ts create mode 100644 smart-contracts/contract-as/tests/utils/spec.ts create mode 100644 smart-contracts/contract/Cargo.toml create mode 100644 smart-contracts/contract/README.md create mode 100644 smart-contracts/contract/src/contract_api/account.rs create mode 100644 smart-contracts/contract/src/contract_api/mod.rs create mode 100644 smart-contracts/contract/src/contract_api/runtime.rs create mode 100644 smart-contracts/contract/src/contract_api/storage.rs create mode 100644 smart-contracts/contract/src/contract_api/system.rs create mode 100644 smart-contracts/contract/src/ext_ffi.rs create mode 100644 smart-contracts/contract/src/handlers.rs create mode 100644 smart-contracts/contract/src/lib.rs create mode 100644 smart-contracts/contract/src/unwrap_or_revert.rs create mode 100644 smart-contracts/contract/tests/version_numbers.rs create mode 100644 smart-contracts/contracts-as/.gitignore create mode 100644 smart-contracts/contracts-as/client/bonding/assembly/index.ts create mode 100644 smart-contracts/contracts-as/client/bonding/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/client/bonding/index.js create mode 100644 smart-contracts/contracts-as/client/bonding/package.json create mode 100644 smart-contracts/contracts-as/client/named-purse-payment/assembly/index.ts create mode 100644 smart-contracts/contracts-as/client/named-purse-payment/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/client/named-purse-payment/index.js create mode 100644 smart-contracts/contracts-as/client/named-purse-payment/package.json create mode 100644 smart-contracts/contracts-as/client/revert/assembly/index.ts create mode 100644 smart-contracts/contracts-as/client/revert/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/client/revert/index.js create mode 100644 smart-contracts/contracts-as/client/revert/package.json create mode 100644 smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/index.ts create mode 100644 smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/client/transfer-to-account-u512/index.js create mode 100644 smart-contracts/contracts-as/client/transfer-to-account-u512/package.json create mode 100644 smart-contracts/contracts-as/client/unbonding/assembly/index.ts create mode 100644 smart-contracts/contracts-as/client/unbonding/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/client/unbonding/index.js create mode 100644 smart-contracts/contracts-as/client/unbonding/package.json create mode 100644 smart-contracts/contracts-as/test/add-update-associated-key/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/add-update-associated-key/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/add-update-associated-key/index.js create mode 100644 smart-contracts/contracts-as/test/add-update-associated-key/package.json create mode 100644 smart-contracts/contracts-as/test/authorized-keys/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/authorized-keys/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/authorized-keys/index.js create mode 100644 smart-contracts/contracts-as/test/authorized-keys/package.json create mode 100644 smart-contracts/contracts-as/test/create-purse-01/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/create-purse-01/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/create-purse-01/index.js create mode 100644 smart-contracts/contracts-as/test/create-purse-01/package.json create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored-caller/index.js create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored-caller/package.json create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored-upgrader/index.js create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored-upgrader/package.json create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored/index.js create mode 100644 smart-contracts/contracts-as/test/do-nothing-stored/package.json create mode 100644 smart-contracts/contracts-as/test/do-nothing/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/do-nothing/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/do-nothing/index.js create mode 100644 smart-contracts/contracts-as/test/do-nothing/package.json create mode 100644 smart-contracts/contracts-as/test/endless-loop/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/endless-loop/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/endless-loop/index.js create mode 100644 smart-contracts/contracts-as/test/endless-loop/package.json create mode 100644 smart-contracts/contracts-as/test/get-arg/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/get-arg/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/get-arg/index.js create mode 100644 smart-contracts/contracts-as/test/get-arg/package.json create mode 100644 smart-contracts/contracts-as/test/get-blocktime/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/get-blocktime/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/get-blocktime/index.js create mode 100644 smart-contracts/contracts-as/test/get-blocktime/package.json create mode 100644 smart-contracts/contracts-as/test/get-caller/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/get-caller/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/get-caller/index.js create mode 100644 smart-contracts/contracts-as/test/get-caller/package.json create mode 100644 smart-contracts/contracts-as/test/get-phase-payment/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/get-phase-payment/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/get-phase-payment/index.js create mode 100644 smart-contracts/contracts-as/test/get-phase-payment/package.json create mode 100644 smart-contracts/contracts-as/test/get-phase/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/get-phase/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/get-phase/index.js create mode 100644 smart-contracts/contracts-as/test/get-phase/package.json create mode 100644 smart-contracts/contracts-as/test/groups/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/groups/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/groups/index.js create mode 100644 smart-contracts/contracts-as/test/groups/package.json create mode 100644 smart-contracts/contracts-as/test/key-management-thresholds/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/key-management-thresholds/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/key-management-thresholds/index.js create mode 100644 smart-contracts/contracts-as/test/key-management-thresholds/package.json create mode 100644 smart-contracts/contracts-as/test/list-named-keys/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/list-named-keys/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/list-named-keys/index.js create mode 100644 smart-contracts/contracts-as/test/list-named-keys/package.json create mode 100644 smart-contracts/contracts-as/test/main-purse/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/main-purse/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/main-purse/index.js create mode 100644 smart-contracts/contracts-as/test/main-purse/package.json create mode 100644 smart-contracts/contracts-as/test/manage-groups/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/manage-groups/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/manage-groups/index.js create mode 100644 smart-contracts/contracts-as/test/manage-groups/package.json create mode 100644 smart-contracts/contracts-as/test/named-keys/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/named-keys/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/named-keys/index.js create mode 100644 smart-contracts/contracts-as/test/named-keys/package.json create mode 100644 smart-contracts/contracts-as/test/overwrite-uref-content/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/overwrite-uref-content/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/overwrite-uref-content/index.js create mode 100644 smart-contracts/contracts-as/test/overwrite-uref-content/package.json create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored-caller/index.js create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored-caller/package.json create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored-upgrader/index.js create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored-upgrader/package.json create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored/index.js create mode 100644 smart-contracts/contracts-as/test/purse-holder-stored/package.json create mode 100644 smart-contracts/contracts-as/test/remove-associated-key/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/remove-associated-key/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/remove-associated-key/index.js create mode 100644 smart-contracts/contracts-as/test/remove-associated-key/package.json create mode 100644 smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/index.js create mode 100644 smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/package.json create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-account-stored/index.js create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-account-stored/package.json create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-account/index.js create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-account/package.json create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/index.ts create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/tsconfig.json create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-purse/index.js create mode 100644 smart-contracts/contracts-as/test/transfer-purse-to-purse/package.json create mode 100644 smart-contracts/contracts/.cargo/config create mode 100644 smart-contracts/contracts/SRE/create-test-node-01/Cargo.toml create mode 100644 smart-contracts/contracts/SRE/create-test-node-01/src/main.rs create mode 100644 smart-contracts/contracts/SRE/create-test-node-02/Cargo.toml create mode 100644 smart-contracts/contracts/SRE/create-test-node-02/src/main.rs create mode 100644 smart-contracts/contracts/SRE/create-test-node-03/Cargo.toml create mode 100644 smart-contracts/contracts/SRE/create-test-node-03/src/main.rs create mode 100644 smart-contracts/contracts/SRE/create-test-node-shared/Cargo.toml create mode 100644 smart-contracts/contracts/SRE/create-test-node-shared/src/lib.rs create mode 100644 smart-contracts/contracts/bench/create-accounts/Cargo.toml create mode 100644 smart-contracts/contracts/bench/create-accounts/src/main.rs create mode 100644 smart-contracts/contracts/bench/create-purses/Cargo.toml create mode 100644 smart-contracts/contracts/bench/create-purses/src/main.rs create mode 100644 smart-contracts/contracts/bench/transfer-to-existing-account/Cargo.toml create mode 100644 smart-contracts/contracts/bench/transfer-to-existing-account/src/main.rs create mode 100644 smart-contracts/contracts/bench/transfer-to-purse/Cargo.toml create mode 100644 smart-contracts/contracts/bench/transfer-to-purse/src/main.rs create mode 100644 smart-contracts/contracts/client/bonding/Cargo.toml create mode 100644 smart-contracts/contracts/client/bonding/src/main.rs create mode 100644 smart-contracts/contracts/client/counter-define/Cargo.toml create mode 100644 smart-contracts/contracts/client/counter-define/src/main.rs create mode 100644 smart-contracts/contracts/client/named-purse-payment/Cargo.toml create mode 100644 smart-contracts/contracts/client/named-purse-payment/src/main.rs create mode 100644 smart-contracts/contracts/client/revert/Cargo.toml create mode 100644 smart-contracts/contracts/client/revert/src/main.rs create mode 100644 smart-contracts/contracts/client/transfer-to-account-stored/Cargo.toml create mode 100644 smart-contracts/contracts/client/transfer-to-account-stored/src/main.rs create mode 100644 smart-contracts/contracts/client/transfer-to-account-u512-stored/Cargo.toml create mode 100644 smart-contracts/contracts/client/transfer-to-account-u512-stored/src/main.rs create mode 100644 smart-contracts/contracts/client/transfer-to-account-u512/Cargo.toml create mode 100644 smart-contracts/contracts/client/transfer-to-account-u512/src/bin/main.rs create mode 100644 smart-contracts/contracts/client/transfer-to-account-u512/src/lib.rs create mode 100644 smart-contracts/contracts/client/transfer-to-account/Cargo.toml create mode 100644 smart-contracts/contracts/client/transfer-to-account/src/bin/main.rs create mode 100644 smart-contracts/contracts/client/transfer-to-account/src/lib.rs create mode 100644 smart-contracts/contracts/client/unbonding/Cargo.toml create mode 100644 smart-contracts/contracts/client/unbonding/src/main.rs create mode 100644 smart-contracts/contracts/explorer/faucet-stored/Cargo.toml create mode 100644 smart-contracts/contracts/explorer/faucet-stored/src/main.rs create mode 100644 smart-contracts/contracts/explorer/faucet/Cargo.toml create mode 100644 smart-contracts/contracts/explorer/faucet/src/bin/main.rs create mode 100644 smart-contracts/contracts/explorer/faucet/src/lib.rs create mode 100644 smart-contracts/contracts/integration/add-associated-key/Cargo.toml create mode 100644 smart-contracts/contracts/integration/add-associated-key/src/main.rs create mode 100644 smart-contracts/contracts/integration/args-multi/Cargo.toml create mode 100644 smart-contracts/contracts/integration/args-multi/src/main.rs create mode 100644 smart-contracts/contracts/integration/args-u32/Cargo.toml create mode 100644 smart-contracts/contracts/integration/args-u32/src/main.rs create mode 100644 smart-contracts/contracts/integration/args-u512/Cargo.toml create mode 100644 smart-contracts/contracts/integration/args-u512/src/main.rs create mode 100644 smart-contracts/contracts/integration/create-named-purse/Cargo.toml create mode 100644 smart-contracts/contracts/integration/create-named-purse/src/main.rs create mode 100644 smart-contracts/contracts/integration/direct-revert/Cargo.toml create mode 100644 smart-contracts/contracts/integration/direct-revert/src/main.rs create mode 100644 smart-contracts/contracts/integration/get-caller-call/Cargo.toml create mode 100644 smart-contracts/contracts/integration/get-caller-call/src/main.rs create mode 100644 smart-contracts/contracts/integration/get-caller-define/Cargo.toml create mode 100644 smart-contracts/contracts/integration/get-caller-define/src/main.rs create mode 100644 smart-contracts/contracts/integration/list-known-urefs-call/Cargo.toml create mode 100644 smart-contracts/contracts/integration/list-known-urefs-call/src/main.rs create mode 100644 smart-contracts/contracts/integration/list-known-urefs-define/Cargo.toml create mode 100644 smart-contracts/contracts/integration/list-known-urefs-define/src/main.rs create mode 100644 smart-contracts/contracts/integration/payment-from-named-purse/Cargo.toml create mode 100644 smart-contracts/contracts/integration/payment-from-named-purse/src/main.rs create mode 100644 smart-contracts/contracts/integration/set-key-thresholds/Cargo.toml create mode 100644 smart-contracts/contracts/integration/set-key-thresholds/src/main.rs create mode 100644 smart-contracts/contracts/integration/subcall-revert-call/Cargo.toml create mode 100644 smart-contracts/contracts/integration/subcall-revert-call/src/main.rs create mode 100644 smart-contracts/contracts/integration/subcall-revert-define/Cargo.toml create mode 100644 smart-contracts/contracts/integration/subcall-revert-define/src/main.rs create mode 100644 smart-contracts/contracts/integration/update-associated-key/Cargo.toml create mode 100644 smart-contracts/contracts/integration/update-associated-key/src/main.rs create mode 100644 smart-contracts/contracts/integration/write-all-types/Cargo.toml create mode 100644 smart-contracts/contracts/integration/write-all-types/src/main.rs create mode 100644 smart-contracts/contracts/profiling/host-function-metrics/Cargo.toml create mode 100644 smart-contracts/contracts/profiling/host-function-metrics/src/lib.rs create mode 100644 smart-contracts/contracts/profiling/simple-transfer/Cargo.toml create mode 100644 smart-contracts/contracts/profiling/simple-transfer/src/main.rs create mode 100644 smart-contracts/contracts/profiling/state-initializer/Cargo.toml create mode 100644 smart-contracts/contracts/profiling/state-initializer/src/main.rs create mode 100644 smart-contracts/contracts/system/mint-install/Cargo.toml create mode 100644 smart-contracts/contracts/system/mint-install/src/main.rs create mode 100644 smart-contracts/contracts/system/mint-token/Cargo.toml create mode 100644 smart-contracts/contracts/system/mint-token/src/bin/main.rs create mode 100644 smart-contracts/contracts/system/mint-token/src/lib.rs create mode 100644 smart-contracts/contracts/system/pos-install/Cargo.toml create mode 100644 smart-contracts/contracts/system/pos-install/src/main.rs create mode 100644 smart-contracts/contracts/system/pos/Cargo.toml create mode 100644 smart-contracts/contracts/system/pos/src/bin/main.rs create mode 100644 smart-contracts/contracts/system/pos/src/lib.rs create mode 100644 smart-contracts/contracts/system/standard-payment-install/Cargo.toml create mode 100644 smart-contracts/contracts/system/standard-payment-install/src/main.rs create mode 100644 smart-contracts/contracts/system/standard-payment/Cargo.toml create mode 100644 smart-contracts/contracts/system/standard-payment/src/bin/main.rs create mode 100644 smart-contracts/contracts/system/standard-payment/src/lib.rs create mode 100644 smart-contracts/contracts/system/test-mint-token/Cargo.toml create mode 100644 smart-contracts/contracts/system/test-mint-token/src/main.rs create mode 100644 smart-contracts/contracts/test/add-gas-subcall/Cargo.toml create mode 100644 smart-contracts/contracts/test/add-gas-subcall/src/main.rs create mode 100644 smart-contracts/contracts/test/add-update-associated-key/Cargo.toml create mode 100644 smart-contracts/contracts/test/add-update-associated-key/src/main.rs create mode 100644 smart-contracts/contracts/test/authorized-keys/Cargo.toml create mode 100644 smart-contracts/contracts/test/authorized-keys/src/main.rs create mode 100644 smart-contracts/contracts/test/contract-context/Cargo.toml create mode 100644 smart-contracts/contracts/test/contract-context/src/main.rs create mode 100644 smart-contracts/contracts/test/create-purse-01/Cargo.toml create mode 100644 smart-contracts/contracts/test/create-purse-01/src/bin/main.rs create mode 100644 smart-contracts/contracts/test/create-purse-01/src/lib.rs create mode 100644 smart-contracts/contracts/test/deserialize-error/Cargo.toml create mode 100644 smart-contracts/contracts/test/deserialize-error/src/main.rs create mode 100644 smart-contracts/contracts/test/do-nothing-stored-caller/Cargo.toml create mode 100644 smart-contracts/contracts/test/do-nothing-stored-caller/src/main.rs create mode 100644 smart-contracts/contracts/test/do-nothing-stored-upgrader/Cargo.toml create mode 100644 smart-contracts/contracts/test/do-nothing-stored-upgrader/src/main.rs create mode 100644 smart-contracts/contracts/test/do-nothing-stored/Cargo.toml create mode 100644 smart-contracts/contracts/test/do-nothing-stored/src/main.rs create mode 100644 smart-contracts/contracts/test/do-nothing/Cargo.toml create mode 100644 smart-contracts/contracts/test/do-nothing/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-221-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-221-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-401-regression-call/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-401-regression-call/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-401-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-401-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-441-rng-state/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-441-rng-state/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-460-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-460-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-532-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-532-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-536-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-536-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-539-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-539-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-549-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-549-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-550-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-550-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-572-regression-create/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-572-regression-create/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-572-regression-escalate/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-572-regression-escalate/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-584-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-584-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-597-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-597-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-598-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-598-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-599-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-599-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-601-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-601-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-771-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-771-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/ee-803-regression/Cargo.toml create mode 100644 smart-contracts/contracts/test/ee-803-regression/src/main.rs create mode 100644 smart-contracts/contracts/test/endless-loop/Cargo.toml create mode 100644 smart-contracts/contracts/test/endless-loop/src/main.rs create mode 100644 smart-contracts/contracts/test/expensive-calculation/Cargo.toml create mode 100644 smart-contracts/contracts/test/expensive-calculation/src/main.rs create mode 100644 smart-contracts/contracts/test/get-arg/Cargo.toml create mode 100644 smart-contracts/contracts/test/get-arg/src/main.rs create mode 100644 smart-contracts/contracts/test/get-blocktime/Cargo.toml create mode 100644 smart-contracts/contracts/test/get-blocktime/src/main.rs create mode 100644 smart-contracts/contracts/test/get-caller-subcall/Cargo.toml create mode 100644 smart-contracts/contracts/test/get-caller-subcall/src/main.rs create mode 100644 smart-contracts/contracts/test/get-caller/Cargo.toml create mode 100644 smart-contracts/contracts/test/get-caller/src/main.rs create mode 100644 smart-contracts/contracts/test/get-phase-payment/Cargo.toml create mode 100644 smart-contracts/contracts/test/get-phase-payment/src/main.rs create mode 100644 smart-contracts/contracts/test/get-phase/Cargo.toml create mode 100644 smart-contracts/contracts/test/get-phase/src/main.rs create mode 100644 smart-contracts/contracts/test/groups/Cargo.toml create mode 100644 smart-contracts/contracts/test/groups/src/main.rs create mode 100644 smart-contracts/contracts/test/key-management-thresholds/Cargo.toml create mode 100644 smart-contracts/contracts/test/key-management-thresholds/src/main.rs create mode 100644 smart-contracts/contracts/test/list-named-keys/Cargo.toml create mode 100644 smart-contracts/contracts/test/list-named-keys/src/main.rs create mode 100644 smart-contracts/contracts/test/local-state-add/Cargo.toml create mode 100644 smart-contracts/contracts/test/local-state-add/src/main.rs create mode 100644 smart-contracts/contracts/test/local-state-stored-caller/Cargo.toml create mode 100644 smart-contracts/contracts/test/local-state-stored-caller/src/main.rs create mode 100644 smart-contracts/contracts/test/local-state-stored-upgraded/Cargo.toml create mode 100644 smart-contracts/contracts/test/local-state-stored-upgraded/src/bin/main.rs create mode 100644 smart-contracts/contracts/test/local-state-stored-upgraded/src/lib.rs create mode 100644 smart-contracts/contracts/test/local-state-stored-upgrader/Cargo.toml create mode 100644 smart-contracts/contracts/test/local-state-stored-upgrader/src/bin/main.rs create mode 100644 smart-contracts/contracts/test/local-state-stored/Cargo.toml create mode 100644 smart-contracts/contracts/test/local-state-stored/src/main.rs create mode 100644 smart-contracts/contracts/test/local-state/Cargo.toml create mode 100644 smart-contracts/contracts/test/local-state/src/bin/main.rs create mode 100644 smart-contracts/contracts/test/local-state/src/lib.rs create mode 100644 smart-contracts/contracts/test/main-purse/Cargo.toml create mode 100644 smart-contracts/contracts/test/main-purse/src/main.rs create mode 100644 smart-contracts/contracts/test/manage-groups/Cargo.toml create mode 100644 smart-contracts/contracts/test/manage-groups/src/main.rs create mode 100644 smart-contracts/contracts/test/measure-gas-subcall/Cargo.toml create mode 100644 smart-contracts/contracts/test/measure-gas-subcall/src/main.rs create mode 100644 smart-contracts/contracts/test/mint-purse/Cargo.toml create mode 100644 smart-contracts/contracts/test/mint-purse/src/main.rs create mode 100644 smart-contracts/contracts/test/modified-mint-caller/Cargo.toml create mode 100644 smart-contracts/contracts/test/modified-mint-caller/src/main.rs create mode 100644 smart-contracts/contracts/test/modified-mint-upgrader/Cargo.toml create mode 100644 smart-contracts/contracts/test/modified-mint-upgrader/src/main.rs create mode 100644 smart-contracts/contracts/test/modified-mint/Cargo.toml create mode 100644 smart-contracts/contracts/test/modified-mint/src/bin/main.rs create mode 100644 smart-contracts/contracts/test/modified-mint/src/lib.rs create mode 100644 smart-contracts/contracts/test/modified-system-upgrader/Cargo.toml create mode 100644 smart-contracts/contracts/test/modified-system-upgrader/src/main.rs create mode 100644 smart-contracts/contracts/test/named-keys/Cargo.toml create mode 100644 smart-contracts/contracts/test/named-keys/src/main.rs create mode 100644 smart-contracts/contracts/test/overwrite-uref-content/Cargo.toml create mode 100644 smart-contracts/contracts/test/overwrite-uref-content/src/main.rs create mode 100644 smart-contracts/contracts/test/pos-bonding/Cargo.toml create mode 100644 smart-contracts/contracts/test/pos-bonding/src/main.rs create mode 100644 smart-contracts/contracts/test/pos-finalize-payment/Cargo.toml create mode 100644 smart-contracts/contracts/test/pos-finalize-payment/src/main.rs create mode 100644 smart-contracts/contracts/test/pos-get-payment-purse/Cargo.toml create mode 100644 smart-contracts/contracts/test/pos-get-payment-purse/src/main.rs create mode 100644 smart-contracts/contracts/test/pos-refund-purse/Cargo.toml create mode 100644 smart-contracts/contracts/test/pos-refund-purse/src/main.rs create mode 100644 smart-contracts/contracts/test/purse-holder-stored-caller/Cargo.toml create mode 100644 smart-contracts/contracts/test/purse-holder-stored-caller/src/main.rs create mode 100644 smart-contracts/contracts/test/purse-holder-stored-upgrader/Cargo.toml create mode 100644 smart-contracts/contracts/test/purse-holder-stored-upgrader/src/main.rs create mode 100644 smart-contracts/contracts/test/purse-holder-stored/Cargo.toml create mode 100644 smart-contracts/contracts/test/purse-holder-stored/src/main.rs create mode 100644 smart-contracts/contracts/test/remove-associated-key/Cargo.toml create mode 100644 smart-contracts/contracts/test/remove-associated-key/src/main.rs create mode 100644 smart-contracts/contracts/test/test-payment-stored/Cargo.toml create mode 100644 smart-contracts/contracts/test/test-payment-stored/src/main.rs create mode 100644 smart-contracts/contracts/test/transfer-main-purse-to-new-purse/Cargo.toml create mode 100644 smart-contracts/contracts/test/transfer-main-purse-to-new-purse/src/main.rs create mode 100644 smart-contracts/contracts/test/transfer-main-purse-to-two-purses/Cargo.toml create mode 100644 smart-contracts/contracts/test/transfer-main-purse-to-two-purses/src/main.rs create mode 100644 smart-contracts/contracts/test/transfer-purse-to-account-stored/Cargo.toml create mode 100644 smart-contracts/contracts/test/transfer-purse-to-account-stored/src/main.rs create mode 100644 smart-contracts/contracts/test/transfer-purse-to-account/Cargo.toml create mode 100644 smart-contracts/contracts/test/transfer-purse-to-account/src/bin/main.rs create mode 100644 smart-contracts/contracts/test/transfer-purse-to-account/src/lib.rs create mode 100644 smart-contracts/contracts/test/transfer-purse-to-purse/Cargo.toml create mode 100644 smart-contracts/contracts/test/transfer-purse-to-purse/src/main.rs create mode 100644 types/Cargo.toml create mode 100644 types/README.md create mode 100644 types/benches/bytesrepr_bench.rs create mode 100644 types/src/access_rights.rs create mode 100644 types/src/account.rs create mode 100644 types/src/api_error.rs create mode 100644 types/src/block_time.rs create mode 100644 types/src/bytesrepr.rs create mode 100644 types/src/cl_type.rs create mode 100644 types/src/cl_value.rs create mode 100644 types/src/contract_wasm.rs create mode 100644 types/src/contracts.rs create mode 100644 types/src/gens.rs create mode 100644 types/src/key.rs create mode 100644 types/src/lib.rs create mode 100644 types/src/mint.rs create mode 100644 types/src/mint/runtime_provider.rs create mode 100644 types/src/mint/storage_provider.rs create mode 100644 types/src/phase.rs create mode 100644 types/src/proof_of_stake.rs create mode 100644 types/src/proof_of_stake/mint_provider.rs create mode 100644 types/src/proof_of_stake/queue.rs create mode 100644 types/src/proof_of_stake/queue_provider.rs create mode 100644 types/src/proof_of_stake/runtime_provider.rs create mode 100644 types/src/proof_of_stake/stakes.rs create mode 100644 types/src/proof_of_stake/stakes_provider.rs create mode 100644 types/src/protocol_version.rs create mode 100644 types/src/runtime_args.rs create mode 100644 types/src/semver.rs create mode 100644 types/src/standard_payment.rs create mode 100644 types/src/standard_payment/account_provider.rs create mode 100644 types/src/standard_payment/mint_provider.rs create mode 100644 types/src/standard_payment/proof_of_stake_provider.rs create mode 100644 types/src/system_contract_errors/mint.rs create mode 100644 types/src/system_contract_errors/mod.rs create mode 100644 types/src/system_contract_errors/pos.rs create mode 100644 types/src/system_contract_type.rs create mode 100644 types/src/transfer_result.rs create mode 100644 types/src/uint.rs create mode 100644 types/src/uref.rs create mode 100644 types/tests/version_numbers.rs diff --git a/.gitignore b/.gitignore index 2f7896d1d1..7f3e8e0a1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,130 @@ -target/ +target-as + +# Criterion puts results in wrong directories inside workspace: https://github.com/bheisler/criterion.rs/issues/192 +target + +# Created by https://www.toptal.com/developers/gitignore/api/rust,node +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +# /target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +# Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# End of https://www.toptal.com/developers/gitignore/api/rust,node diff --git a/Cargo.lock b/Cargo.lock index 83c9f46867..d11d9e3031 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,44 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "add-associated-key" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "add-gas-subcall" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "add-update-associated-key" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "addr2line" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602d785912f476e480434627e8732e6766b760c045bbf897d9dfaa9f4fbd399c" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler32" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" + [[package]] name = "aho-corasick" version = "0.7.13" @@ -33,6 +72,30 @@ version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" +[[package]] +name = "args-multi" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "args-u32" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "args-u512" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -45,6 +108,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" +[[package]] +name = "assert_matches" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7deb0a829ca7bcfaf5da70b073a8d128619259a7be8216a355e23f00763059e5" + [[package]] name = "atty" version = "0.2.14" @@ -56,6 +125,14 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "authorized-keys" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "autocfg" version = "0.1.7" @@ -68,6 +145,36 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +[[package]] +name = "backtrace" +version = "0.3.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" + +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" +dependencies = [ + "byteorder", + "safemem", +] + [[package]] name = "base64" version = "0.11.0" @@ -90,6 +197,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" + [[package]] name = "bitflags" version = "0.5.0" @@ -146,6 +268,26 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "bonding" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "bstr" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "buf_redux" version = "0.8.4" @@ -174,6 +316,16 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + [[package]] name = "bytes" version = "0.5.5" @@ -183,32 +335,121 @@ dependencies = [ "loom", ] +[[package]] +name = "casperlabs-contract" +version = "0.6.0" +dependencies = [ + "casperlabs-types", + "failure", + "hex_fmt", + "version-sync", + "wee_alloc", +] + +[[package]] +name = "casperlabs-engine-grpc-server" +version = "0.20.0" +dependencies = [ + "casperlabs-node", + "casperlabs-types", + "clap", + "ctrlc", + "dirs", + "grpc", + "lmdb", + "log 0.4.8", + "parity-wasm", + "proptest", + "protobuf", + "protoc-rust-grpc", + "rand 0.7.3", +] + +[[package]] +name = "casperlabs-engine-test-support" +version = "0.8.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-engine-grpc-server", + "casperlabs-node", + "casperlabs-types", + "grpc", + "lazy_static", + "lmdb", + "log 0.4.8", + "num-traits", + "protobuf", + "rand 0.7.3", + "version-sync", +] + +[[package]] +name = "casperlabs-engine-tests" +version = "0.1.0" +dependencies = [ + "assert_matches", + "base16", + "casperlabs-contract", + "casperlabs-engine-grpc-server", + "casperlabs-engine-test-support", + "casperlabs-node", + "casperlabs-types", + "clap", + "criterion", + "crossbeam-channel", + "env_logger", + "grpc", + "lazy_static", + "log 0.4.8", + "num-traits", + "rand 0.7.3", + "serde_json", + "tempfile", + "wabt", +] + [[package]] name = "casperlabs-node" version = "0.1.0" dependencies = [ "ansi_term 0.12.1", "anyhow", + "assert_matches", + "base16", "bincode", "blake2", - "bytes", + "bytes 0.5.5", + "casperlabs-types", + "chrono", "derive_more", "directories", "ed25519-dalek", "either", "enum-iterator", + "failure", "fake_instant", - "futures", + "futures 0.3.5", "getrandom", "hex", "hex_fmt", + "hostname", "http", + "itertools 0.8.2", "lazy_static", "libc", + "linked-hash-map", "lmdb", + "log 0.4.8", "maplit", + "num", + "num-derive", + "num-traits", "openssl", + "parity-wasm", + "parking_lot 0.10.2", "pnet", + "proptest", + "pwasm-utils", "rand 0.7.3", "rand_chacha 0.2.2", "rand_core 0.5.1", @@ -218,25 +459,54 @@ dependencies = [ "serde", "serde-big-array", "serde_json", - "smallvec", + "smallvec 1.4.1", "structopt", "tempfile", "thiserror", - "tokio", + "tokio 0.2.21", "tokio-openssl", "tokio-serde", "tokio-util", "toml", "tracing", "tracing-subscriber", + "uuid", + "wabt", "warp", + "wasmi", +] + +[[package]] +name = "casperlabs-types" +version = "0.6.0" +dependencies = [ + "base16", + "bitflags 1.2.1", + "blake2", + "failure", + "hex_fmt", + "num-derive", + "num-integer", + "num-traits", + "proptest", + "uint", + "version-sync", +] + +[[package]] +name = "cast" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" +dependencies = [ + "rustc_version", ] [[package]] name = "cc" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c1f1d60091c1b73e2b1f4560ab419204b178e625fa945ded7b660becd2bd46" +checksum = "0fde55d2a2bfaa4c9668bbc63f531fbdeee3ffe188f4662511ce2c22b3eedebe" [[package]] name = "cfg-if" @@ -246,9 +516,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "chrono" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0fee792e164f78f5fe0c296cc2eb3688a2ca2b70cdff33040922d298203f0c4" +checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6" dependencies = [ "num-integer", "num-traits", @@ -288,12 +558,29 @@ dependencies = [ "bitflags 1.2.1", ] +[[package]] +name = "cmake" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56268c17a6248366d66d4a47a3381369d068cce8409bb1716ed77ea32163bb" +dependencies = [ + "cc", +] + [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "contract-context" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "core-foundation" version = "0.7.0" @@ -311,123 +598,530 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] -name = "crossbeam-utils" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +name = "counter-define" +version = "0.1.0" dependencies = [ - "autocfg 1.0.0", - "cfg-if", - "lazy_static", + "casperlabs-contract", + "casperlabs-types", ] [[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +name = "create-accounts" +version = "0.1.0" dependencies = [ - "generic-array", - "subtle 1.0.0", + "casperlabs-contract", + "casperlabs-types", ] [[package]] -name = "curve25519-dalek" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5" +name = "create-named-purse" +version = "0.1.0" dependencies = [ - "byteorder", - "digest", - "rand_core 0.5.1", - "subtle 2.2.3", - "zeroize", + "casperlabs-contract", + "casperlabs-types", ] [[package]] -name = "derivative" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" +name = "create-purse-01" +version = "0.1.0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "casperlabs-contract", + "casperlabs-types", ] [[package]] -name = "derive_more" -version = "0.99.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298998b1cf6b5b2c8a7b023dfd45821825ce3ba8a8af55c921a0e734e4653f76" +name = "create-purses" +version = "0.1.0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "casperlabs-contract", + "casperlabs-types", ] [[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +name = "create-test-node-01" +version = "0.1.0" dependencies = [ - "generic-array", + "create-test-node-shared", ] [[package]] -name = "directories" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" +name = "create-test-node-02" +version = "0.1.0" dependencies = [ - "cfg-if", - "dirs-sys", + "create-test-node-shared", ] [[package]] -name = "dirs-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +name = "create-test-node-03" +version = "0.1.0" dependencies = [ - "libc", - "redox_users", - "winapi 0.3.9", + "create-test-node-shared", ] [[package]] -name = "dtoa" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" +name = "create-test-node-shared" +version = "0.1.0" +dependencies = [ + "base16", + "casperlabs-contract", + "casperlabs-types", +] [[package]] -name = "ed25519-dalek" -version = "1.0.0-pre.3" +name = "criterion" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" +checksum = "70daa7ceec6cf143990669a04c7df13391d55fb27bd4079d252fca774ba244d8" dependencies = [ - "clear_on_drop", - "curve25519-dalek", - "rand 0.7.3", + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools 0.9.0", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", "serde", - "sha2", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", ] [[package]] -name = "either" -version = "1.5.3" +name = "criterion-plot" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d" +dependencies = [ + "cast", + "itertools 0.9.0", +] [[package]] -name = "encoding_rs" -version = "0.8.23" +name = "crossbeam-channel" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" +checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" dependencies = [ - "cfg-if", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg 1.0.0", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg 1.0.0", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array", + "subtle 1.0.0", +] + +[[package]] +name = "csv" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "ctrlc" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a4ba686dff9fa4c1c9636ce1010b0cf98ceb421361b0bb3d6faeec43bd217a7" +dependencies = [ + "nix", + "winapi 0.3.9", +] + +[[package]] +name = "curve25519-dalek" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5" +dependencies = [ + "byteorder", + "digest", + "rand_core 0.5.1", + "subtle 2.2.3", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + +[[package]] +name = "derive_more" +version = "0.99.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298998b1cf6b5b2c8a7b023dfd45821825ce3ba8a8af55c921a0e734e4653f76" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + +[[package]] +name = "deserialize-error" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "direct-revert" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "directories" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" +dependencies = [ + "cfg-if", + "dirs-sys", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + +[[package]] +name = "do-nothing" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", +] + +[[package]] +name = "do-nothing-stored" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "do-nothing-stored-caller" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "do-nothing-stored-upgrader" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "create-purse-01", +] + +[[package]] +name = "dtoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" + +[[package]] +name = "ed25519-dalek" +version = "1.0.0-pre.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" +dependencies = [ + "clear_on_drop", + "curve25519-dalek", + "rand 0.7.3", + "serde", + "sha2", +] + +[[package]] +name = "ee-221-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-401-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-401-regression-call" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-441-rng-state" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-460-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-532-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", +] + +[[package]] +name = "ee-536-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-539-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-549-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-550-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-572-regression-create" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-572-regression-escalate" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-584-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-597-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-598-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-599-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-601-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-771-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "ee-803-regression" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" + +[[package]] +name = "encoding_rs" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endless-loop" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", ] [[package]] @@ -445,9 +1139,52 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log 0.4.8", + "regex", + "termcolor", +] + +[[package]] +name = "expensive-calculation" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", + "synstructure", ] [[package]] @@ -462,6 +1199,23 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3006df2e7bf21592b4983931164020b02f54eefdc1e35b2f70147858cc1e20ad" +[[package]] +name = "faucet" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "faucet-stored" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "faucet", +] + [[package]] name = "fnv" version = "1.0.7" @@ -505,6 +1259,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "futures" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" + [[package]] name = "futures" version = "0.3.5" @@ -536,6 +1296,16 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" +[[package]] +name = "futures-cpupool" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" +dependencies = [ + "futures 0.1.29", + "num_cpus", +] + [[package]] name = "futures-executor" version = "0.3.5" @@ -560,9 +1330,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", ] [[package]] @@ -597,7 +1367,7 @@ dependencies = [ "pin-utils", "proc-macro-hack", "proc-macro-nested", - "slab", + "slab 0.4.2", ] [[package]] @@ -622,6 +1392,70 @@ dependencies = [ "typenum", ] +[[package]] +name = "get-arg" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "get-blocktime" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "get-caller" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "get-caller-call" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "get-caller-define" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "get-caller-subcall" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "get-phase" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "get-phase-payment" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "getrandom" version = "0.1.14" @@ -633,19 +1467,63 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" + [[package]] name = "glob" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" +[[package]] +name = "groups" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "grpc" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aaf1d741fe6f3413f1f9f71b99f5e4e26776d563475a8a53ce53a73a8534c1d" +dependencies = [ + "base64 0.9.3", + "bytes 0.4.12", + "futures 0.1.29", + "futures-cpupool", + "httpbis", + "log 0.4.8", + "protobuf", + "tls-api", + "tls-api-stub", + "tokio-core", + "tokio-io", + "tokio-tls-api", +] + +[[package]] +name = "grpc-compiler" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907274ce8ee7b40a0d0b0db09022ea22846a47cfb1fc8ad2c983c70001b4ffb1" +dependencies = [ + "protobuf", + "protobuf-codegen", +] + [[package]] name = "h2" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" dependencies = [ - "bytes", + "bytes 0.5.5", "fnv", "futures-core", "futures-sink", @@ -653,11 +1531,17 @@ dependencies = [ "http", "indexmap", "log 0.4.8", - "slab", - "tokio", + "slab 0.4.2", + "tokio 0.2.21", "tokio-util", ] +[[package]] +name = "half" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177" + [[package]] name = "headers" version = "0.3.2" @@ -666,7 +1550,7 @@ checksum = "ed18eb2459bf1a09ad2d6b1547840c3e5e62882fa09b9a6a20b1de8e3228848f" dependencies = [ "base64 0.12.3", "bitflags 1.2.1", - "bytes", + "bytes 0.5.5", "headers-core", "http", "mime 0.3.16", @@ -694,9 +1578,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" +checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" dependencies = [ "libc", ] @@ -713,13 +1597,33 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" +[[package]] +name = "host-function-metrics" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "rand 0.7.3", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi 0.3.9", +] + [[package]] name = "http" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" dependencies = [ - "bytes", + "bytes 0.5.5", "fnv", "itoa", ] @@ -730,7 +1634,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" dependencies = [ - "bytes", + "bytes 0.5.5", "http", ] @@ -740,13 +1644,44 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +[[package]] +name = "httpbis" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7689cfa896b2a71da4f16206af167542b75d242b6906313e53857972a92d5614" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.29", + "futures-cpupool", + "log 0.4.8", + "net2", + "tls-api", + "tls-api-stub", + "tokio-core", + "tokio-io", + "tokio-timer 0.1.2", + "tokio-tls-api", + "tokio-uds 0.1.7", + "unix_socket", + "void", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "hyper" version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6e7655b9594024ad0ee439f3b5a7299369dc2a3f459b47c696f9ff676f9aa1f" dependencies = [ - "bytes", + "bytes 0.5.5", "futures-channel", "futures-core", "futures-util", @@ -759,24 +1694,35 @@ dependencies = [ "pin-project", "socket2", "time", - "tokio", + "tokio 0.2.21", "tower-service", "want", ] [[package]] name = "hyper-tls" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" +checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed" dependencies = [ - "bytes", + "bytes 0.5.5", "hyper", "native-tls", - "tokio", + "tokio 0.2.21", "tokio-tls", ] +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.2.0" @@ -803,7 +1749,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" dependencies = [ - "bytes", + "bytes 0.5.5", ] [[package]] @@ -824,6 +1770,24 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.6" @@ -849,6 +1813,14 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "key-management-thresholds" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -861,6 +1833,36 @@ version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + +[[package]] +name = "list-known-urefs-call" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "list-known-urefs-define" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "list-named-keys" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "lmdb" version = "0.8.0" @@ -883,6 +1885,65 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "local-state" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", +] + +[[package]] +name = "local-state-add" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "local-state-stored" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "local-state", +] + +[[package]] +name = "local-state-stored-caller" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "local-state-stored-upgraded" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "local-state", +] + +[[package]] +name = "local-state-stored-upgrader" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "local-state-stored-upgraded", +] + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.3.9" @@ -899,6 +1960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" dependencies = [ "cfg-if", + "serde", ] [[package]] @@ -912,12 +1974,34 @@ dependencies = [ "scoped-tls 0.1.2", ] +[[package]] +name = "main-purse" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "manage-groups" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchers" version = "0.0.1" @@ -933,12 +2017,47 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "measure-gas-subcall" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +[[package]] +name = "memoffset" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "memory_units" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + [[package]] name = "mime" version = "0.2.6" @@ -976,6 +2095,40 @@ dependencies = [ "unicase 2.6.0", ] +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "mint-install" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "mint-token", +] + +[[package]] +name = "mint-purse" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "mint-token" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "mio" version = "0.6.22" @@ -991,10 +2144,21 @@ dependencies = [ "log 0.4.8", "miow", "net2", - "slab", + "slab 0.4.2", "winapi 0.2.8", ] +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + [[package]] name = "miow" version = "0.2.1" @@ -1007,6 +2171,43 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "modified-mint" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "mint-token", +] + +[[package]] +name = "modified-mint-caller" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "modified-mint-upgrader" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "modified-mint", +] + +[[package]] +name = "modified-system-upgrader" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "modified-mint", + "pos", + "standard-payment", +] + [[package]] name = "multipart" version = "0.16.1" @@ -1025,6 +2226,22 @@ dependencies = [ "twoway", ] +[[package]] +name = "named-keys" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "named-purse-payment" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "native-tls" version = "0.2.4" @@ -1054,6 +2271,64 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nix" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +dependencies = [ + "bitflags 1.2.1", + "cc", + "cfg-if", + "libc", + "void", +] + +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + [[package]] name = "num-integer" version = "0.1.43" @@ -1064,6 +2339,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg 1.0.0", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.12" @@ -1083,12 +2381,24 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" + [[package]] name = "once_cell" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" +[[package]] +name = "oorandom" +version = "11.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c" + [[package]] name = "opaque-debug" version = "0.2.3" @@ -1138,6 +2448,84 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "overwrite-uref-content" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "parity-wasm" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" + +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +dependencies = [ + "lock_api", + "parking_lot_core 0.6.2", + "rustc_version", +] + +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api", + "parking_lot_core 0.7.2", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "rustc_version", + "smallvec 0.6.13", + "winapi 0.3.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "smallvec 1.4.1", + "winapi 0.3.9", +] + +[[package]] +name = "payment-from-named-purse" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1198,9 +2586,9 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", ] [[package]] @@ -1221,6 +2609,18 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +[[package]] +name = "plotters" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d1685fbe7beba33de0330629da9d955ac75bd54f33d7b79f9a895590124f6bb" +dependencies = [ + "js-sys", + "num-traits", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "pnet" version = "0.26.0" @@ -1310,6 +2710,57 @@ dependencies = [ "pnet_sys", ] +[[package]] +name = "pos" +version = "0.1.0" +dependencies = [ + "base16", + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "pos-bonding" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "pos-finalize-payment" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "pos-get-payment-purse" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "pos-install" +version = "0.1.0" +dependencies = [ + "base16", + "casperlabs-contract", + "casperlabs-types", + "pos", +] + +[[package]] +name = "pos-refund-purse" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "ppv-lite86" version = "0.2.8" @@ -1323,9 +2774,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880" dependencies = [ "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", "version_check 0.9.2", ] @@ -1335,9 +2786,9 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", "syn-mid", "version_check 0.9.2", ] @@ -1354,6 +2805,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", +] + [[package]] name = "proc-macro2" version = "1.0.18" @@ -1363,19 +2823,159 @@ dependencies = [ "unicode-xid 0.2.1", ] +[[package]] +name = "proptest" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c477819b845fe023d33583ebf10c9f62518c8d79a0960ba5c36d6ac8a55a5b" +dependencies = [ + "bit-set", + "bitflags 1.2.1", + "byteorder", + "lazy_static", + "num-traits", + "quick-error", + "rand 0.6.5", + "rand_chacha 0.1.1", + "rand_xorshift 0.1.1", + "regex-syntax", + "rusty-fork", + "tempfile", +] + +[[package]] +name = "protobuf" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70731852eec72c56d11226c8a5f96ad5058a3dab73647ca5f7ee351e464f2571" +dependencies = [ + "bytes 0.4.12", +] + +[[package]] +name = "protobuf-codegen" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d74b9cbbf2ac9a7169c85a3714ec16c51ee9ec7cfd511549527e9a7df720795" +dependencies = [ + "protobuf", +] + +[[package]] +name = "protoc" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d9500ea1488a61aa96da139039b78a92eef64a0f3c82d38173729f0ad73cf8" +dependencies = [ + "log 0.4.8", +] + +[[package]] +name = "protoc-rust" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea851ddc77c57935a586099f6e1f8bd7b4d366379498f25b8882ed02e0222bf" +dependencies = [ + "protobuf", + "protobuf-codegen", + "protoc", + "tempfile", +] + +[[package]] +name = "protoc-rust-grpc" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b959e379834057693e0e5a228bc3939aa8e4fee895da1531f69b6e7e74c80d6" +dependencies = [ + "grpc-compiler", + "protobuf", + "protoc", + "protoc-rust", + "tempdir", +] + +[[package]] +name = "pulldown-cmark" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1b74cc784b038a9921fd1a48310cc2e238101aa8ae0b94201e2d85121dd68b5" +dependencies = [ + "bitflags 1.2.1", + "memchr", + "unicase 2.6.0", +] + +[[package]] +name = "purse-holder-stored" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "purse-holder-stored-caller" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "purse-holder-stored-upgrader" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "pwasm-utils" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7a12f176deee919f4ba55326ee17491c8b707d0987aed822682c821b660192" +dependencies = [ + "byteorder", + "log 0.4.8", + "parity-wasm", +] + [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + [[package]] name = "quote" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.18", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", ] [[package]] @@ -1392,7 +2992,7 @@ dependencies = [ "rand_isaac", "rand_jitter", "rand_os", - "rand_pcg", + "rand_pcg 0.1.2", "rand_xorshift 0.1.1", "winapi 0.3.9", ] @@ -1408,6 +3008,7 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", + "rand_pcg 0.2.1", ] [[package]] @@ -1516,6 +3117,15 @@ dependencies = [ "rand_core 0.4.2", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_xorshift" version = "0.1.1" @@ -1534,6 +3144,31 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rayon" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080" +dependencies = [ + "autocfg 1.0.0", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280" +dependencies = [ + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -1588,6 +3223,14 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" +[[package]] +name = "remove-associated-key" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -1604,7 +3247,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680" dependencies = [ "base64 0.12.3", - "bytes", + "bytes 0.5.5", "encoding_rs", "futures-core", "futures-util", @@ -1618,19 +3261,27 @@ dependencies = [ "mime 0.3.16", "mime_guess 2.0.3", "native-tls", - "percent-encoding", + "percent-encoding 2.1.0", "pin-project-lite", "serde", "serde_urlencoded", - "tokio", + "tokio 0.2.21", "tokio-tls", - "url", + "url 2.1.1", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "winreg", ] +[[package]] +name = "revert" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "rmp" version = "0.8.9" @@ -1664,6 +3315,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc-serialize" version = "0.3.24" @@ -1679,6 +3342,18 @@ dependencies = [ "semver", ] +[[package]] +name = "rusty-fork" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.5" @@ -1691,6 +3366,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.19" @@ -1713,6 +3397,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "security-framework" version = "0.4.4" @@ -1742,14 +3432,20 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", ] [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "semver-parser" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46e1121e8180c12ff69a742aabc4f310542b6ccb69f1691689ac17fdf8618aa" [[package]] name = "serde" @@ -1770,15 +3466,25 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_cbor" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", ] [[package]] @@ -1801,7 +3507,15 @@ dependencies = [ "dtoa", "itoa", "serde", - "url", + "url 2.1.1", +] + +[[package]] +name = "set-key-thresholds" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", ] [[package]] @@ -1837,12 +3551,26 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "simple-transfer" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "siphasher" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +[[package]] +name = "slab" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" + [[package]] name = "slab" version = "0.4.2" @@ -1851,9 +3579,18 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.4.0" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" +checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f" [[package]] name = "socket2" @@ -1867,6 +3604,37 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "standard-payment" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "standard-payment-install" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "standard-payment", +] + +[[package]] +name = "state-initializer" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.8.0" @@ -1892,9 +3660,25 @@ checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118" dependencies = [ "heck", "proc-macro-error", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + +[[package]] +name = "subcall-revert-call" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "subcall-revert-define" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", ] [[package]] @@ -1909,14 +3693,25 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", +] + [[package]] name = "syn" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.18", + "quote 1.0.7", "unicode-xid 0.2.1", ] @@ -1926,9 +3721,21 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", + "unicode-xid 0.2.1", ] [[package]] @@ -1980,6 +3787,16 @@ dependencies = [ "unicode-xid 0.0.3", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + [[package]] name = "tempfile" version = "3.1.0" @@ -2004,6 +3821,32 @@ dependencies = [ "winapi 0.2.8", ] +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "test-mint-token" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "test-payment-stored" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "standard-payment", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -2023,116 +3866,384 @@ dependencies = [ ] [[package]] -name = "thiserror-impl" -version = "1.0.20" +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "tinytemplate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" + +[[package]] +name = "tls-api" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049c03787a0595182357fbd487577947f4351b78ce20c3668f6d49f17feb13d1" +dependencies = [ + "log 0.4.8", +] + +[[package]] +name = "tls-api-stub" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a0cc8c149724db9de7d73a0e1bc80b1a74f5394f08c6f301e11f9c35fa061e" +dependencies = [ + "tls-api", + "void", +] + +[[package]] +name = "tokio" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.29", + "mio", + "num_cpus", + "tokio-codec", + "tokio-current-thread", + "tokio-executor", + "tokio-fs", + "tokio-io", + "tokio-reactor", + "tokio-sync", + "tokio-tcp", + "tokio-threadpool", + "tokio-timer 0.2.13", + "tokio-udp", + "tokio-uds 0.2.7", +] + +[[package]] +name = "tokio" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" +dependencies = [ + "bytes 0.5.5", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "slab 0.4.2", + "tokio-macros", +] + +[[package]] +name = "tokio-codec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.29", + "tokio-io", +] + +[[package]] +name = "tokio-core" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.29", + "iovec", + "log 0.4.8", + "mio", + "scoped-tls 0.1.2", + "tokio 0.1.22", + "tokio-executor", + "tokio-io", + "tokio-reactor", + "tokio-timer 0.2.13", +] + +[[package]] +name = "tokio-current-thread" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e" +dependencies = [ + "futures 0.1.29", + "tokio-executor", +] + +[[package]] +name = "tokio-executor" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" +dependencies = [ + "crossbeam-utils", + "futures 0.1.29", +] + +[[package]] +name = "tokio-fs" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4" +dependencies = [ + "futures 0.1.29", + "tokio-io", + "tokio-threadpool", +] + +[[package]] +name = "tokio-io" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.29", + "log 0.4.8", +] + +[[package]] +name = "tokio-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + +[[package]] +name = "tokio-openssl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c4b08c5f4208e699ede3df2520aca2e82401b2de33f45e96696a074480be594" +dependencies = [ + "openssl", + "tokio 0.2.21", +] + +[[package]] +name = "tokio-reactor" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" +dependencies = [ + "crossbeam-utils", + "futures 0.1.29", + "lazy_static", + "log 0.4.8", + "mio", + "num_cpus", + "parking_lot 0.9.0", + "slab 0.4.2", + "tokio-executor", + "tokio-io", + "tokio-sync", +] + +[[package]] +name = "tokio-serde" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebdd897b01021779294eb09bb3b52b6e11b0747f9f7e333a84bef532b656de99" +dependencies = [ + "bytes 0.5.5", + "derivative", + "futures 0.3.5", + "pin-project", + "rmp-serde", + "serde", +] + +[[package]] +name = "tokio-sync" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" +dependencies = [ + "fnv", + "futures 0.1.29", +] + +[[package]] +name = "tokio-tcp" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" dependencies = [ - "proc-macro2", - "quote", - "syn", + "bytes 0.4.12", + "futures 0.1.29", + "iovec", + "mio", + "tokio-io", + "tokio-reactor", ] [[package]] -name = "thread_local" -version = "1.0.1" +name = "tokio-threadpool" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" dependencies = [ + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "futures 0.1.29", "lazy_static", + "log 0.4.8", + "num_cpus", + "slab 0.4.2", + "tokio-executor", ] [[package]] -name = "time" -version = "0.1.43" +name = "tokio-timer" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "6131e780037787ff1b3f8aad9da83bca02438b72277850dd6ad0d455e0e20efc" dependencies = [ - "libc", - "winapi 0.3.9", + "futures 0.1.29", + "slab 0.3.0", ] [[package]] -name = "tinyvec" -version = "0.3.3" +name = "tokio-timer" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" +checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" +dependencies = [ + "crossbeam-utils", + "futures 0.1.29", + "slab 0.4.2", + "tokio-executor", +] [[package]] -name = "tokio" -version = "0.2.21" +name = "tokio-tls" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" +checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" dependencies = [ - "bytes", - "fnv", - "futures-core", - "iovec", - "lazy_static", - "memchr", - "mio", - "num_cpus", - "pin-project-lite", - "slab", - "tokio-macros", + "native-tls", + "tokio 0.2.21", ] [[package]] -name = "tokio-macros" -version = "0.2.5" +name = "tokio-tls-api" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" +checksum = "68d0e040d5b1f4cfca70ec4f371229886a5de5bb554d272a4a8da73004a7b2c9" dependencies = [ - "proc-macro2", - "quote", - "syn", + "futures 0.1.29", + "tls-api", + "tokio-io", ] [[package]] -name = "tokio-openssl" -version = "0.4.0" +name = "tokio-tungstenite" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c4b08c5f4208e699ede3df2520aca2e82401b2de33f45e96696a074480be594" +checksum = "b8b8fe88007ebc363512449868d7da4389c9400072a3f666f212c7280082882a" dependencies = [ - "openssl", - "tokio", + "futures 0.3.5", + "log 0.4.8", + "pin-project", + "tokio 0.2.21", + "tungstenite", ] [[package]] -name = "tokio-serde" -version = "0.6.1" +name = "tokio-udp" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebdd897b01021779294eb09bb3b52b6e11b0747f9f7e333a84bef532b656de99" +checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82" dependencies = [ - "bytes", - "derivative", - "futures", - "pin-project", - "rmp-serde", - "serde", + "bytes 0.4.12", + "futures 0.1.29", + "log 0.4.8", + "mio", + "tokio-codec", + "tokio-io", + "tokio-reactor", ] [[package]] -name = "tokio-tls" -version = "0.3.1" +name = "tokio-uds" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" +checksum = "65ae5d255ce739e8537221ed2942e0445f4b3b813daebac1c0050ddaaa3587f9" dependencies = [ - "native-tls", - "tokio", + "bytes 0.4.12", + "futures 0.1.29", + "iovec", + "libc", + "log 0.3.9", + "mio", + "mio-uds", + "tokio-core", + "tokio-io", ] [[package]] -name = "tokio-tungstenite" -version = "0.10.1" +name = "tokio-uds" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b8fe88007ebc363512449868d7da4389c9400072a3f666f212c7280082882a" +checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0" dependencies = [ - "futures", + "bytes 0.4.12", + "futures 0.1.29", + "iovec", + "libc", "log 0.4.8", - "pin-project", - "tokio", - "tungstenite", + "mio", + "mio-uds", + "tokio-codec", + "tokio-io", + "tokio-reactor", ] [[package]] @@ -2141,12 +4252,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" dependencies = [ - "bytes", + "bytes 0.5.5", "futures-core", "futures-sink", "log 0.4.8", "pin-project-lite", - "tokio", + "tokio 0.2.21", ] [[package]] @@ -2181,9 +4292,9 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99bbad0de3fd923c9c3232ead88510b783e5a4d16a6154adffa3d53308de984c" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", ] [[package]] @@ -2230,12 +4341,103 @@ dependencies = [ "serde", "serde_json", "sharded-slab", - "smallvec", + "smallvec 1.4.1", "tracing-core", "tracing-log", "tracing-serde", ] +[[package]] +name = "transfer-main-purse-to-new-purse" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "transfer-main-purse-to-two-purses" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "transfer-purse-to-account" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "transfer-purse-to-account-stored" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "transfer-purse-to-account", +] + +[[package]] +name = "transfer-purse-to-purse" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "transfer-to-account" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "transfer-to-account-stored" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "transfer-to-account", +] + +[[package]] +name = "transfer-to-account-u512" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "transfer-to-account-u512-stored" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", + "transfer-to-account-u512", +] + +[[package]] +name = "transfer-to-existing-account" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "transfer-to-purse" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "try-lock" version = "0.2.2" @@ -2250,14 +4452,14 @@ checksum = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" dependencies = [ "base64 0.11.0", "byteorder", - "bytes", + "bytes 0.5.5", "http", "httparse", "input_buffer", "log 0.4.8", "rand 0.7.3", "sha-1", - "url", + "url 2.1.1", "utf-8", ] @@ -2276,6 +4478,26 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +[[package]] +name = "uint" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173cd16430c206dc1a430af8a89a0e9c076cf15cb42b4aedb10e8cc8fee73681" +dependencies = [ + "byteorder", + "crunchy", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "unbonding" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "unicase" version = "1.4.2" @@ -2330,21 +4552,56 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + [[package]] name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "unix_socket" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "update-associated-key" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna 0.1.5", + "matches", + "percent-encoding 1.0.1", +] + [[package]] name = "url" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" dependencies = [ - "idna", + "idna 0.2.0", "matches", - "percent-encoding", + "percent-encoding 2.1.0", ] [[package]] @@ -2359,6 +4616,16 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +dependencies = [ + "rand 0.7.3", + "serde", +] + [[package]] name = "vcpkg" version = "0.2.10" @@ -2371,6 +4638,22 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version-sync" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "844f3d3a2467f15cb999f5af7775f6e108ac546d4f42365832ed4c755404f806" +dependencies = [ + "itertools 0.8.2", + "proc-macro2 0.4.30", + "pulldown-cmark", + "regex", + "semver-parser 0.9.0", + "syn 0.15.44", + "toml", + "url 1.7.2", +] + [[package]] name = "version_check" version = "0.1.5" @@ -2383,6 +4666,55 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "wabt" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5c5c1286c6e578416982609f47594265f9d489f9b836157d403ad605a46693" +dependencies = [ + "serde", + "serde_derive", + "serde_json", + "wabt-sys", +] + +[[package]] +name = "wabt-sys" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c695f98f7eb81fd4e2f6b65301ccc916a950dc2265eeefc4d376b34ce666df" +dependencies = [ + "cc", + "cmake", + "glob", +] + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi 0.3.9", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" @@ -2399,8 +4731,8 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e95175b7a927258ecbb816bdada3cc469cb68593e7940b96a60f4af366a9970" dependencies = [ - "bytes", - "futures", + "bytes 0.5.5", + "futures 0.3.5", "headers", "http", "hyper", @@ -2413,7 +4745,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio", + "tokio 0.2.21", "tokio-tungstenite", "tower-service", "urlencoding", @@ -2446,9 +4778,9 @@ dependencies = [ "bumpalo", "lazy_static", "log 0.4.8", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", "wasm-bindgen-shared", ] @@ -2470,7 +4802,7 @@ version = "0.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fcfd5ef6eec85623b4c6e844293d4516470d8f19cd72d0d12246017eb9060b8" dependencies = [ - "quote", + "quote 1.0.7", "wasm-bindgen-macro-support", ] @@ -2480,9 +4812,9 @@ version = "0.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9adff9ee0e94b926ca81b57f57f86d5545cdcb1d259e21ec9bdd95b901754c75" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2493,6 +4825,29 @@ version = "0.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7b90ea6c632dd06fd765d44542e234d5e63d9bb917ecd64d79778a13bd79ae" +[[package]] +name = "wasmi" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf617d864d25af3587aa745529f7aaa541066c876d57e050c0d0c85c61c92aff" +dependencies = [ + "libc", + "memory_units 0.3.0", + "num-rational", + "num-traits", + "parity-wasm", + "wasmi-validation", +] + +[[package]] +name = "wasmi-validation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea78c597064ba73596099281e2f4cfc019075122a65cdda3205af94f0b264d93" +dependencies = [ + "parity-wasm", +] + [[package]] name = "web-sys" version = "0.3.41" @@ -2503,6 +4858,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if", + "libc", + "memory_units 0.4.0", + "winapi 0.3.9", +] + [[package]] name = "winapi" version = "0.2.8" @@ -2531,6 +4898,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2546,6 +4922,14 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "write-all-types" +version = "0.1.0" +dependencies = [ + "casperlabs-contract", + "casperlabs-types", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 4f32c3306e..927736f099 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,84 +1,31 @@ -[package] -name = "casperlabs-node" -version = "0.1.0" -authors = ["Marc Brinkmann ", "Fraser Hutchison "] -edition = "2018" -description = "The CasperLabs blockchain node" -documentation = "https://docs.rs/casperlabs-node" -readme = "README.md" -homepage = "https://casperlabs.io" -repository = "https://github.com/CasperLabs/casperlabs-node" -license-file = "LICENSE" -publish = false # Prevent accidental `cargo publish` for now. -default-run = "casperlabs-node" +[workspace] -[dependencies] -ansi_term = "0.12.1" -anyhow = "1.0.28" -bincode = "1.2.1" -blake2 = { version = "0.8.1", default-features = false } -bytes = "0.5.4" -derive_more = "0.99.7" -directories = "2.0.2" -ed25519-dalek = { version = "1.0.0-pre.3", default-features = false, features = ["rand", "serde", "u64_backend"] } -either = "1.5.3" -enum-iterator = "0.6.0" -futures = "0.3.5" -getrandom = "0.1.14" -hex = "0.4.2" -hex_fmt = "0.3.0" -http = "0.2.1" -lazy_static = "1.4.0" -libc = "0.2.71" -lmdb = "0.8.0" -maplit = "1.0.2" -openssl = "0.10.29" -rand = "0.7.3" -rand_chacha = "0.2.2" -reqwest = "0.10.6" -rmp-serde = "0.14.3" -serde = { version = "1.0.110", features = ["derive"] } -serde-big-array = "0.3.0" -serde_json = "1.0.55" -smallvec = "1.4.0" -structopt = "0.3.14" -tempfile = "3.1.0" -thiserror = "1.0.18" -tokio = { version = "0.2.20", features = ["macros", "rt-threaded", "sync", "tcp", "time", "blocking"] } -tokio-openssl = "0.4.0" -tokio-serde = { version = "0.6.1", features = ["messagepack"] } -tokio-util = { version = "0.3.1", features = ["codec"] } -toml = "0.5.6" -tracing = "0.1.14" -tracing-subscriber = "0.2.5" -warp = "0.2.3" - -[dev-dependencies] -fake_instant = "0.4.0" -pnet = "0.26.0" -rand_xorshift = { version = "~0.2.0" } -rand_core = "0.5.1" +members = [ + "smart-contracts/contract", + "smart-contracts/contracts/[!.]*/*", + "grpc/server", + "grpc/test-support", + "grpc/tests", + "types", + "node", +] -[features] -vendored-openssl = ['openssl/vendored'] +default-members = [ + "smart-contracts/contract", + "grpc/server", + "grpc/test-support", + "grpc/tests", + "types", + "node", +] -[[bin]] -name = "casperlabs-node" -path = "src/apps/node/main.rs" -bench = false -doctest = false -test = false +# Include debug symbols in the release build of `casperlabs-engine-tests` so that `simple-transfer` will yield useful +# perf data. +[profile.release.package.casperlabs-engine-tests] +debug = true -[[bin]] -name = "casperlabs-client" -path = "src/apps/client/main.rs" -bench = false -doctest = false -test = false +[profile.release] +lto = true -[package.metadata.deb] -features = ["vendored-openssl"] -assets = [ - ["./target/release/casperlabs-node","/usr/bin/casperlabs-node", "755"], - ["./target/release/casperlabs-client","/usr/bin/casperlabs-client", "755"] -] +[profile.bench] +lto = true diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..980fbc828a --- /dev/null +++ b/Makefile @@ -0,0 +1,271 @@ +# This supports environments where $HOME/.cargo/env has not been sourced (CI, CLion Makefile runner) +CARGO = $(or $(shell which cargo), $(HOME)/.cargo/bin/cargo) +RUSTUP = $(or $(shell which rustup), $(HOME)/.cargo/bin/rustup) +NPM = $(or $(shell which npm), /usr/bin/npm) + +RUST_TOOLCHAIN := nightly + +CARGO := $(CARGO) $(CARGO_OPTS) + +EE_DIR = $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) + +# Rust Contracts +# Directory names should match crate names +BENCH = $(shell find ./smart-contracts/contracts/bench -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +CLIENT = $(shell find ./smart-contracts/contracts/client -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +EXPLORER = $(shell find ./smart-contracts/contracts/explorer -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +INTEGRATION = $(shell find ./smart-contracts/contracts/integration -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +PROFILING = $(shell find ./smart-contracts/contracts/profiling -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +SRE = $(shell find ./smart-contracts/contracts/SRE -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +SYSTEM = $(shell find ./smart-contracts/contracts/system -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +TEST = $(shell find ./smart-contracts/contracts/test -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) + +BENCH_CONTRACTS := $(patsubst %, build-contract-rs/%, $(BENCH)) +CLIENT_CONTRACTS := $(patsubst %, build-contract-rs/%, $(CLIENT)) +EXPLORER_CONTRACTS := $(patsubst %, build-contract-rs/%, $(EXPLORER)) +PROFILING_CONTRACTS := $(patsubst %, build-contract-rs/%, $(PROFILING)) +SRE_CONTRACTS := $(patsubst %, build-contract-rs/%, $(SRE)) +TEST_CONTRACTS := $(patsubst %, build-contract-rs/%, $(TEST)) + +# AssemblyScript Contracts +CLIENT_CONTRACTS_AS = $(shell find ./smart-contracts/contracts-as/client -mindepth 1 -maxdepth 1 -type d) +TEST_CONTRACTS_AS = $(shell find ./smart-contracts/contracts-as/test -mindepth 1 -maxdepth 1 -type d) + +CLIENT_CONTRACTS_AS := $(patsubst %, build-contract-as/%, $(CLIENT_CONTRACTS_AS)) +TEST_CONTRACTS_AS := $(patsubst %, build-contract-as/%, $(TEST_CONTRACTS_AS)) + +INTEGRATION += \ + endless-loop \ + local-state \ + modified-system-upgrader \ + pos-bonding \ + remove-associated-key \ + standard-payment \ + transfer-to-account-u512 + +HIGHWAY_CONTRACTS += \ + pos-install \ + pos + +SYSTEM_CONTRACTS := $(patsubst %, build-contract-rs/%, $(SYSTEM)) +SYSTEM_CONTRACTS_FEATURED := $(patsubst %, build-system-contract-featured-rs/%, $(SYSTEM)) + +CONTRACT_TARGET_DIR = target/wasm32-unknown-unknown/release +CONTRACT_TARGET_DIR_AS = target-as +PACKAGED_SYSTEM_CONTRACTS = mint_install.wasm pos_install.wasm standard_payment_install.wasm +TOOL_TARGET_DIR = cargo-casperlabs/target +TOOL_WASM_DIR = cargo-casperlabs/wasm +ENGINE_CORE_TARGET_DIR = engine-core/target +ENGINE_CORE_WASM_DIR = engine-core/wasm + +CRATES_WITH_DOCS_RS_MANIFEST_TABLE = \ + contract \ + engine-core \ + engine-grpc-server \ + engine-shared \ + engine-storage \ + engine-test-support \ + engine-wasm-prep \ + mint \ + proof-of-stake \ + standard-payment \ + types + +CRATES_WITH_DOCS_RS_MANIFEST_TABLE := $(patsubst %, doc-stable/%, $(CRATES_WITH_DOCS_RS_MANIFEST_TABLE)) + +.PHONY: all +all: build build-contracts + +.PHONY: build +build: + $(CARGO) build $(CARGO_FLAGS) + +build-contract-rs/%: + $(CARGO) build \ + --release $(filter-out --release, $(CARGO_FLAGS)) \ + --package $* \ + --target wasm32-unknown-unknown + +build-system-contract-featured-rs/%: + $(CARGO) build \ + --release $(filter-out --release, $(CARGO_FLAGS)) \ + --manifest-path "contracts/system/$*/Cargo.toml" $(if $(FEATURES),$(if $(filter $(HIGHWAY_CONTRACTS), $*),--features $(FEATURES))) \ + --target wasm32-unknown-unknown + +build-contracts-rs: \ + $(BENCH_CONTRACTS) \ + $(CLIENT_CONTRACTS) \ + $(EXPLORER_CONTRACTS) \ + $(INTEGRATION_CONTRACTS) \ + $(PROFILING_CONTRACTS) \ + $(SRE_CONTRACTS) \ + $(SYSTEM_CONTRACTS) \ + $(TEST_CONTRACTS) + +build-contracts-enable-bonding-rs: FEATURES := enable-bonding +build-contracts-enable-bonding-rs: \ + $(BENCH_CONTRACTS) \ + $(CLIENT_CONTRACTS) \ + $(EXPLORER_CONTRACTS) \ + $(INTEGRATION_CONTRACTS) \ + $(PROFILING_CONTRACTS) \ + $(SRE_CONTRACTS) \ + $(SYSTEM_CONTRACTS_FEATURED) \ + $(TEST_CONTRACTS) + +.PHONY: build-system-contracts +build-system-contracts: $(SYSTEM_CONTRACTS) + +build-contract-as/%: + cd $* && $(NPM) run asbuild + +.PHONY: build-contracts-as +build-contracts-as: \ + $(CLIENT_CONTRACTS_AS) \ + $(TEST_CONTRACTS_AS) \ + $(EXAMPLE_CONTRACTS_AS) + +.PHONY: build-contracts +build-contracts: build-contracts-rs build-contracts-as + +.PHONY: test-rs +test-rs: + $(CARGO) test $(CARGO_FLAGS) --all -- --nocapture + +.PHONY: test-as +test-as: setup-as + cd contract-as && npm run asbuild && npm run test + +.PHONY: test +test: test-rs test-as + +.PHONY: test-contracts-rs +test-contracts-rs: build-contracts-rs + $(CARGO) test $(CARGO_FLAGS) -p casperlabs-engine-tests -- --ignored --nocapture + $(CARGO) test $(CARGO_FLAGS) --manifest-path "grpc/tests/Cargo.toml" --features "use-system-contracts" -- --ignored --nocapture + +.PHONY: test-contracts-enable-bonding-rs +test-contracts-enable-bonding-rs: build-contracts-enable-bonding-rs + $(CARGO) test $(CARGO_FLAGS) --manifest-path "grpc/tests/Cargo.toml" --features "enable-bonding" -- --ignored --nocapture + $(CARGO) test $(CARGO_FLAGS) --manifest-path "grpc/tests/Cargo.toml" --features "enable-bonding,use-system-contracts" -- --ignored --nocapture + +.PHONY: test-contracts-as +test-contracts-as: build-contracts-rs build-contracts-as + @# see https://github.com/rust-lang/cargo/issues/5015#issuecomment-515544290 + $(CARGO) test $(CARGO_FLAGS) --manifest-path "grpc/tests/Cargo.toml" --features "use-as-wasm" -- --ignored --nocapture + +.PHONY: test-contracts +test-contracts: test-contracts-rs test-contracts-as + +.PHONY: check-format +check-format: + $(CARGO) fmt --all -- --check + +.PHONY: format +format: + $(CARGO) fmt --all + +.PHONY: lint +lint: + $(CARGO) clippy --all-targets --all -- -D warnings -A renamed_and_removed_lints + +.PHONY: audit +audit: + $(CARGO) generate-lockfile + $(CARGO) audit + +.PHONY: build-docs-stable-rs +build-docs-stable-rs: $(CRATES_WITH_DOCS_RS_MANIFEST_TABLE) + +doc-stable/%: + $(CARGO) +stable doc $(CARGO_FLAGS) --manifest-path "$*/Cargo.toml" --features "no-unstable-features" --no-deps + +.PHONY: check-rs +check-rs: \ + build-docs-stable-rs \ + build \ + check-format \ + lint \ + audit \ + test-rs \ + test-contracts-rs \ + test-contracts-enable-bonding-rs + +.PHONY: check +check: \ + build \ + check-format \ + lint \ + audit \ + test \ + test-contracts + +.PHONY: clean +clean: + rm -f comm/.rpm + rm -rf $(CONTRACT_TARGET_DIR_AS) + rm -rf $(TOOL_TARGET_DIR) + rm -rf $(TOOL_WASM_DIR) + rm -rf $(ENGINE_CORE_TARGET_DIR) + rm -rf $(ENGINE_CORE_WASM_DIR) + $(CARGO) clean + +.PHONY: deb +deb: + $(CARGO) build --release -p casperlabs-engine-grpc-server + cd grpc/server && $(CARGO) deb -p casperlabs-engine-grpc-server --no-build + +grpc/server/.rpm: + cd grpc/server && $(CARGO) rpm init + +.PHONY: rpm +rpm: grpc/server/.rpm + cd grpc/server && $(CARGO) rpm build + +target/system-contracts.tar.gz: $(SYSTEM_CONTRACTS) + tar -czf $@ -C $(CONTRACT_TARGET_DIR) $(PACKAGED_SYSTEM_CONTRACTS) + +.PHONY: package-system-contracts +package-system-contracts: target/system-contracts.tar.gz + +.PHONY: package +package: + cd contract && $(CARGO) package + +.PHONY: publish +publish: + ./publish.sh + +.PHONY: bench +bench: build-contracts-rs + $(CARGO) bench + +.PHONY: setup-cargo-packagers +setup-cargo-packagers: + $(CARGO) install cargo-rpm || exit 0 + $(CARGO) install cargo-deb || exit 0 + +.PHONY: setup-audit +setup-audit: + $(CARGO) install cargo-audit + +.PHONY: setup-rs +setup-rs: rust-toolchain + $(RUSTUP) update + $(RUSTUP) toolchain install $(RUST_TOOLCHAIN) + $(RUSTUP) target add --toolchain $(RUST_TOOLCHAIN) wasm32-unknown-unknown + +.PHONY: setup-stable-rs +setup-stable-rs: RUST_TOOLCHAIN := stable +setup-stable-rs: setup-rs + +.PHONY: setup-nightly-rs +setup-nightly-rs: RUST_TOOLCHAIN := nightly +setup-nightly-rs: setup-rs + +.PHONY: setup-as +setup-as: contract-as/package.json + cd contract-as && $(NPM) ci + +.PHONY: setup +setup: setup-rs setup-as diff --git a/grpc/server/.rpm/casperlabs-engine-grpc-server.spec b/grpc/server/.rpm/casperlabs-engine-grpc-server.spec new file mode 100644 index 0000000000..7072bc3ff6 --- /dev/null +++ b/grpc/server/.rpm/casperlabs-engine-grpc-server.spec @@ -0,0 +1,64 @@ +%define __spec_install_post %{nil} +%define __os_install_post %{_dbpath}/brp-compress +%define debug_package %{nil} + +Name: casperlabs-engine-grpc-server +Summary: Wasm execution engine for CasperLabs smart contracts. +Version: @@VERSION@@ +Release: @@RELEASE@@ +License: CasperLabs Open Source License (COSL) +Group: Applications/System +Source0: %{name}-%{version}.tar.gz +URL: https://casperlabs.io + +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root + +%description +%{summary} + +%prep +%setup -q + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot} +cp -a * %{buildroot} + +%post +# Default Variables +# --- +DEFAULT_USERNAME="casperlabs" +DEFAULT_DATA_DIRECTORY="/var/lib/${DEFAULT_USERNAME}" + +# User Creation +# --- +# Assure DEFAULT_USERNAME user exists +if id -u ${DEFAULT_USERNAME} >/dev/null 2>&1; then + echo "User ${DEFAULT_USERNAME} already exists." +else + adduser --no-create-home --user-group --system ${DEFAULT_USERNAME} +fi + +# Creation of Files/Directories +# --- +# Assure DEFAULT_DATA_DIRECTORY is available for state data +if [ -d ${DEFAULT_DATA_DIRECTORY} ] ; then + echo "Directory ${DEFAULT_DATA_DIRECTORY} already exists." +else + mkdir -p ${DEFAULT_DATA_DIRECTORY} +fi + +# Files/Directories Owner +# --- +# Assure DEFAULT_DATA_DIRECTORY is owned by DEFAULT_USERNAME +if [ -d ${DEFAULT_DATA_DIRECTORY} ] ; then + chown ${DEFAULT_USERNAME}:${DEFAULT_USERNAME} ${DEFAULT_DATA_DIRECTORY} +fi + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%{_bindir}/* +/lib/systemd/system/casperlabs-engine-grpc-server.service diff --git a/grpc/server/Cargo.toml b/grpc/server/Cargo.toml new file mode 100644 index 0000000000..caf83b6335 --- /dev/null +++ b/grpc/server/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "casperlabs-engine-grpc-server" +version = "0.20.0" +authors = ["Mateusz Górski "] +edition = "2018" +description = "Wasm execution engine for CasperLabs smart contracts." +readme = "README.md" +documentation = "https://docs.rs/casperlabs-engine-grpc-server" +homepage = "https://casperlabs.io" +repository = "https://github.com/CasperLabs/CasperLabs/tree/master/execution-engine/engine-grpc-server" +# this is required by 'cargo rpm' +license-file = "../../LICENSE" +include = [ + "**/*.rs", + "Cargo.toml", + "protobuf/io/casperlabs/casper/consensus/state.proto", + "protobuf/io/casperlabs/ipc/ipc.proto", + "protobuf/io/casperlabs/ipc/transforms.proto", +] + +[dependencies] +clap = "2" +ctrlc = "3" +dirs = "2" +grpc = "0.6.1" +lmdb = "0.8" +log = "0.4.8" +proptest = "0.9.4" +protobuf = "=2.8" + +types = { version = "0.6.0", path = "../../types", package = "casperlabs-types", features = ["std", "gens"] } +node = { path = "../../node", package = "casperlabs-node", features = ["gens"] } + +[build-dependencies] +protoc-rust-grpc = "0.6.1" + +[dev-dependencies] +parity-wasm = "0.41.0" +rand = "0.7.2" + +[features] +test-support = ["node/test-support"] +no-unstable-features = [ + "types/no-unstable-features", +] + +[[bin]] +name = "casperlabs-engine-grpc-server" +path = "src/main.rs" + +[package.metadata.rpm.cargo] +buildflags = ["--release"] + +[package.metadata.rpm.targets] +casperlabs-engine-grpc-server = { path = "/usr/bin/casperlabs-engine-grpc-server" } + +[package.metadata.rpm.files] +"../packaging/casperlabs-engine-grpc-server.service" = { path = "/lib/systemd/system/casperlabs-engine-grpc-server.service" } + +[package.metadata.deb] +maintainer-scripts="debian" +assets = [ + ["packaging/casperlabs-engine-grpc-server.service", "/lib/systemd/system/casperlabs-engine-grpc-server.service", "644"], + ["../../target/release/casperlabs-engine-grpc-server", "/usr/bin/casperlabs-engine-grpc-server", "755"] +] + +[package.metadata.docs.rs] +features = ["no-unstable-features"] diff --git a/grpc/server/README.md b/grpc/server/README.md new file mode 100644 index 0000000000..c765ed89f9 --- /dev/null +++ b/grpc/server/README.md @@ -0,0 +1,14 @@ +# `casperlabs-engine-grpc-server` + +[![LOGO](https://raw.githubusercontent.com/CasperLabs/CasperLabs/master/CasperLabs_Logo_Horizontal_RGB.png)](https://casperlabs.io/) + +[![Build Status](https://drone-auto.casperlabs.io/api/badges/CasperLabs/CasperLabs/status.svg?branch=dev)](http://drone-auto.casperlabs.io/CasperLabs/CasperLabs) +[![Crates.io](https://img.shields.io/crates/v/casperlabs-engine-grpc-server)](https://crates.io/crates/casperlabs-engine-grpc-server) +[![Documentation](https://docs.rs/casperlabs-engine-grpc-server/badge.svg)](https://docs.rs/casperlabs-engine-grpc-server) +[![License](https://img.shields.io/badge/license-COSL-blue.svg)](https://github.com/CasperLabs/CasperLabs/blob/master/LICENSE) + +Wasm execution engine for CasperLabs smart contracts. + +## License + +Licensed under the [CasperLabs Open Source License (COSL)](https://github.com/CasperLabs/CasperLabs/blob/master/LICENSE). diff --git a/grpc/server/build.rs b/grpc/server/build.rs new file mode 100644 index 0000000000..8bc8d7d018 --- /dev/null +++ b/grpc/server/build.rs @@ -0,0 +1,63 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +const PROTOBUF_DIR: &str = "generated_protobuf"; +const WORKAROUND_COMMENT: &str = "// workaround for https://github.com/rust-lang/rfcs/issues/752"; + +// The generated file needs to be sourced via `include!` which doesn't work where the file has top- +// level inner attributes (see https://github.com/rust-lang/rfcs/issues/752). +// +// To work around this issue, we add 'pub mod proto { }' around the contents. +fn wrap_file_contents(target_dir: &Path, file_name_without_suffix: &str) { + let generated_file = target_dir + .join(file_name_without_suffix) + .with_extension("rs"); + + let contents = fs::read_to_string(&generated_file) + .unwrap_or_else(|_| panic!("should read {}", generated_file.display())); + fs::write( + &generated_file, + &format!( + "pub mod {} {{ {}\n\n{}\n}}", + file_name_without_suffix, WORKAROUND_COMMENT, contents + ), + ) + .unwrap_or_else(|_| panic!("should write {}", generated_file.display())); +} + +fn main() { + println!("cargo:rerun-if-changed=protobuf/io/casperlabs/casper/consensus/state.proto"); + println!("cargo:rerun-if-changed=protobuf/io/casperlabs/ipc/ipc.proto"); + println!("cargo:rerun-if-changed=protobuf/io/casperlabs/ipc/transforms.proto"); + + let target_dir = PathBuf::from(format!( + "{}/../../../../{}", + env::var("OUT_DIR").expect("should have env var OUT_DIR set"), + PROTOBUF_DIR + )); + fs::create_dir_all(&target_dir) + .unwrap_or_else(|_| panic!("should create dir {}", target_dir.display())); + + protoc_rust_grpc::run(protoc_rust_grpc::Args { + out_dir: target_dir.to_str().unwrap(), + input: &[ + "protobuf/io/casperlabs/casper/consensus/state.proto", + "protobuf/io/casperlabs/ipc/ipc.proto", + "protobuf/io/casperlabs/ipc/transforms.proto", + ], + includes: &[ + "protobuf/", + "protobuf/io/casperlabs/casper/consensus", + "protobuf/io/casperlabs/ipc", + ], + rust_protobuf: true, + }) + .expect("protoc-rust-grpc"); + + wrap_file_contents(&target_dir, "state"); + wrap_file_contents(&target_dir, "ipc"); + wrap_file_contents(&target_dir, "transforms"); + wrap_file_contents(&target_dir, "ipc_grpc"); +} diff --git a/grpc/server/debian/postinst b/grpc/server/debian/postinst new file mode 100644 index 0000000000..1bfd962abc --- /dev/null +++ b/grpc/server/debian/postinst @@ -0,0 +1,33 @@ +#! /usr/bin/env bash + +set -e + +# Default Variables +# --- +DEFAULT_USERNAME="casperlabs" +DEFAULT_DATA_DIRECTORY="/var/lib/${DEFAULT_USERNAME}" + +# User Creation +# --- +# Assure DEFAULT_USERNAME user exists +if id -u ${DEFAULT_USERNAME} >/dev/null 2>&1; then + echo "User ${DEFAULT_USERNAME} already exists." +else + adduser --no-create-home --group --system ${DEFAULT_USERNAME} +fi + +# Creation of Files/Directories +# --- +# Assure DEFAULT_DATA_DIRECTORY is available for state data +if [ -d ${DEFAULT_DATA_DIRECTORY} ] ; then + echo "Directory ${DEFAULT_DATA_DIRECTORY} already exists." +else + mkdir -p ${DEFAULT_DATA_DIRECTORY} +fi + +# Files/Directories Owner +# --- +# Assure DEFAULT_DATA_DIRECTORY is owned by DEFAULT_USERNAME +if [ -d ${DEFAULT_DATA_DIRECTORY} ] ; then + chown ${DEFAULT_USERNAME}:${DEFAULT_USERNAME} ${DEFAULT_DATA_DIRECTORY} +fi diff --git a/grpc/server/packaging/casperlabs-engine-grpc-server.service b/grpc/server/packaging/casperlabs-engine-grpc-server.service new file mode 100644 index 0000000000..0b7f66cba8 --- /dev/null +++ b/grpc/server/packaging/casperlabs-engine-grpc-server.service @@ -0,0 +1,13 @@ +[Unit] +Description=CasperLabs Engine GRPC Server +After=network.target +Before=casperlabs-node.service +BindsTo=casperlabs-node.service + +[Service] +ExecStart=/usr/bin/casperlabs-engine-grpc-server -d /var/lib/casperlabs /var/lib/casperlabs/.casper-node.sock +User=casperlabs +Restart=no + +[Install] +WantedBy=multi-user.target diff --git a/grpc/server/protobuf/google/api/annotations.proto b/grpc/server/protobuf/google/api/annotations.proto new file mode 100644 index 0000000000..85c361b47f --- /dev/null +++ b/grpc/server/protobuf/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/grpc/server/protobuf/google/api/http.proto b/grpc/server/protobuf/google/api/http.proto new file mode 100644 index 0000000000..b2977f5147 --- /dev/null +++ b/grpc/server/protobuf/google/api/http.proto @@ -0,0 +1,376 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/grpc/server/protobuf/google/protobuf/descriptor.proto b/grpc/server/protobuf/google/protobuf/descriptor.proto new file mode 100644 index 0000000000..a2102d7aa9 --- /dev/null +++ b/grpc/server/protobuf/google/protobuf/descriptor.proto @@ -0,0 +1,885 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// The messages in this file describe the definitions found in .proto files. +// A valid .proto file can be translated directly to a FileDescriptorProto +// without any other information (e.g. without reading its imports). + + +syntax = "proto2"; + +package google.protobuf; + +option go_package = "github.com/golang/protobuf/protoc-gen-go/descriptor;descriptor"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DescriptorProtos"; +option csharp_namespace = "Google.Protobuf.Reflection"; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// descriptor.proto must be optimized for speed because reflection-based +// algorithms don't work during bootstrapping. +option optimize_for = SPEED; + +// The protocol compiler can output a FileDescriptorSet containing the .proto +// files it parses. +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; +} + +// Describes a complete .proto file. +message FileDescriptorProto { + optional string name = 1; // file name, relative to root of source tree + optional string package = 2; // e.g. "foo", "foo.bar", etc. + + // Names of files imported by this file. + repeated string dependency = 3; + // Indexes of the public imported files in the dependency list above. + repeated int32 public_dependency = 10; + // Indexes of the weak imported files in the dependency list. + // For Google-internal migration only. Do not use. + repeated int32 weak_dependency = 11; + + // All top-level definitions in this file. + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + + optional FileOptions options = 8; + + // This field contains optional information about the original source code. + // You may safely remove this entire field without harming runtime + // functionality of the descriptors -- the information is needed only by + // development tools. + optional SourceCodeInfo source_code_info = 9; + + // The syntax of the proto file. + // The supported values are "proto2" and "proto3". + optional string syntax = 12; +} + +// Describes a message type. +message DescriptorProto { + optional string name = 1; + + repeated FieldDescriptorProto field = 2; + repeated FieldDescriptorProto extension = 6; + + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + + message ExtensionRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + + optional ExtensionRangeOptions options = 3; + } + repeated ExtensionRange extension_range = 5; + + repeated OneofDescriptorProto oneof_decl = 8; + + optional MessageOptions options = 7; + + // Range of reserved tag numbers. Reserved tag numbers may not be used by + // fields or extension ranges in the same message. Reserved ranges may + // not overlap. + message ReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + } + repeated ReservedRange reserved_range = 9; + // Reserved field names, which may not be used by fields in the same message. + // A given name may only be reserved once. + repeated string reserved_name = 10; +} + +message ExtensionRangeOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// Describes a field within a message. +message FieldDescriptorProto { + enum Type { + // 0 is reserved for errors. + // Order is weird for historical reasons. + TYPE_DOUBLE = 1; + TYPE_FLOAT = 2; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + TYPE_INT64 = 3; + TYPE_UINT64 = 4; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + TYPE_INT32 = 5; + TYPE_FIXED64 = 6; + TYPE_FIXED32 = 7; + TYPE_BOOL = 8; + TYPE_STRING = 9; + // Tag-delimited aggregate. + // Group type is deprecated and not supported in proto3. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. + TYPE_GROUP = 10; + TYPE_MESSAGE = 11; // Length-delimited aggregate. + + // New in version 2. + TYPE_BYTES = 12; + TYPE_UINT32 = 13; + TYPE_ENUM = 14; + TYPE_SFIXED32 = 15; + TYPE_SFIXED64 = 16; + TYPE_SINT32 = 17; // Uses ZigZag encoding. + TYPE_SINT64 = 18; // Uses ZigZag encoding. + } + + enum Label { + // 0 is reserved for errors + LABEL_OPTIONAL = 1; + LABEL_REQUIRED = 2; + LABEL_REPEATED = 3; + } + + optional string name = 1; + optional int32 number = 3; + optional Label label = 4; + + // If type_name is set, this need not be set. If both this and type_name + // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + optional Type type = 5; + + // For message and enum types, this is the name of the type. If the name + // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + // rules are used to find the type (i.e. first the nested types within this + // message are searched, then within the parent, on up to the root + // namespace). + optional string type_name = 6; + + // For extensions, this is the name of the type being extended. It is + // resolved in the same manner as type_name. + optional string extendee = 2; + + // For numeric types, contains the original text representation of the value. + // For booleans, "true" or "false". + // For strings, contains the default text contents (not escaped in any way). + // For bytes, contains the C escaped value. All bytes >= 128 are escaped. + // TODO(kenton): Base-64 encode? + optional string default_value = 7; + + // If set, gives the index of a oneof in the containing type's oneof_decl + // list. This field is a member of that oneof. + optional int32 oneof_index = 9; + + // JSON name of this field. The value is set by protocol compiler. If the + // user has set a "json_name" option on this field, that option's value + // will be used. Otherwise, it's deduced from the field's name by converting + // it to camelCase. + optional string json_name = 10; + + optional FieldOptions options = 8; +} + +// Describes a oneof. +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} + +// Describes an enum type. +message EnumDescriptorProto { + optional string name = 1; + + repeated EnumValueDescriptorProto value = 2; + + optional EnumOptions options = 3; + + // Range of reserved numeric values. Reserved values may not be used by + // entries in the same enum. Reserved ranges may not overlap. + // + // Note that this is distinct from DescriptorProto.ReservedRange in that it + // is inclusive such that it can appropriately represent the entire int32 + // domain. + message EnumReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Inclusive. + } + + // Range of reserved numeric values. Reserved numeric values may not be used + // by enum values in the same enum declaration. Reserved ranges may not + // overlap. + repeated EnumReservedRange reserved_range = 4; + + // Reserved enum value names, which may not be reused. A given name may only + // be reserved once. + repeated string reserved_name = 5; +} + +// Describes a value within an enum. +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + + optional EnumValueOptions options = 3; +} + +// Describes a service. +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + + optional ServiceOptions options = 3; +} + +// Describes a method of a service. +message MethodDescriptorProto { + optional string name = 1; + + // Input and output type names. These are resolved in the same way as + // FieldDescriptorProto.type_name, but must refer to a message type. + optional string input_type = 2; + optional string output_type = 3; + + optional MethodOptions options = 4; + + // Identifies if client streams multiple client messages + optional bool client_streaming = 5 [default = false]; + // Identifies if server streams multiple server messages + optional bool server_streaming = 6 [default = false]; +} + + +// =================================================================== +// Options + +// Each of the definitions above may have "options" attached. These are +// just annotations which may cause code to be generated slightly differently +// or may contain hints for code that manipulates protocol messages. +// +// Clients may define custom options as extensions of the *Options messages. +// These extensions may not yet be known at parsing time, so the parser cannot +// store the values in them. Instead it stores them in a field in the *Options +// message called uninterpreted_option. This field must have the same name +// across all *Options messages. We then use this field to populate the +// extensions when we build a descriptor, at which point all protos have been +// parsed and so all extensions are known. +// +// Extension numbers for custom options may be chosen as follows: +// * For options which will only be used within a single application or +// organization, or for experimental options, use field numbers 50000 +// through 99999. It is up to you to ensure that you do not use the +// same number for multiple options. +// * For options which will be published and used publicly by multiple +// independent entities, e-mail protobuf-global-extension-registry@google.com +// to reserve extension numbers. Simply provide your project name (e.g. +// Objective-C plugin) and your project website (if available) -- there's no +// need to explain how you intend to use them. Usually you only need one +// extension number. You can declare multiple options with only one extension +// number by putting them in a sub-message. See the Custom Options section of +// the docs for examples: +// https://developers.google.com/protocol-buffers/docs/proto#options +// If this turns out to be popular, a web service will be set up +// to automatically assign option numbers. + +message FileOptions { + + // Sets the Java package where classes generated from this .proto will be + // placed. By default, the proto package is used, but this is often + // inappropriate because proto packages do not normally start with backwards + // domain names. + optional string java_package = 1; + + + // If set, all the classes from the .proto file are wrapped in a single + // outer class with the given name. This applies to both Proto1 + // (equivalent to the old "--one_java_file" option) and Proto2 (where + // a .proto always translates to a single class, but you may want to + // explicitly choose the class name). + optional string java_outer_classname = 8; + + // If set true, then the Java code generator will generate a separate .java + // file for each top-level message, enum, and service defined in the .proto + // file. Thus, these types will *not* be nested inside the outer class + // named by java_outer_classname. However, the outer class will still be + // generated to contain the file's getDescriptor() method as well as any + // top-level extensions defined in the file. + optional bool java_multiple_files = 10 [default = false]; + + // This option does nothing. + optional bool java_generate_equals_and_hash = 20 [deprecated=true]; + + // If set true, then the Java2 code generator will generate code that + // throws an exception whenever an attempt is made to assign a non-UTF-8 + // byte sequence to a string field. + // Message reflection will do the same. + // However, an extension field still accepts non-UTF-8 byte sequences. + // This option has no effect on when used with the lite runtime. + optional bool java_string_check_utf8 = 27 [default = false]; + + + // Generated classes can be optimized for speed or code size. + enum OptimizeMode { + SPEED = 1; // Generate complete code for parsing, serialization, + // etc. + CODE_SIZE = 2; // Use ReflectionOps to implement these methods. + LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. + } + optional OptimizeMode optimize_for = 9 [default = SPEED]; + + // Sets the Go package where structs generated from this .proto will be + // placed. If omitted, the Go package will be derived from the following: + // - The basename of the package import path, if provided. + // - Otherwise, the package statement in the .proto file, if present. + // - Otherwise, the basename of the .proto file, without extension. + optional string go_package = 11; + + + + + // Should generic services be generated in each language? "Generic" services + // are not specific to any particular RPC system. They are generated by the + // main code generators in each language (without additional plugins). + // Generic services were the only kind of service generation supported by + // early versions of google.protobuf. + // + // Generic services are now considered deprecated in favor of using plugins + // that generate code specific to your particular RPC system. Therefore, + // these default to false. Old code which depends on generic services should + // explicitly set them to true. + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + optional bool php_generic_services = 42 [default = false]; + + // Is this file deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for everything in the file, or it will be completely ignored; in the very + // least, this is a formalization for deprecating files. + optional bool deprecated = 23 [default = false]; + + // Enables the use of arenas for the proto messages in this file. This applies + // only to generated classes for C++. + optional bool cc_enable_arenas = 31 [default = false]; + + + // Sets the objective c class prefix which is prepended to all objective c + // generated classes from this .proto. There is no default. + optional string objc_class_prefix = 36; + + // Namespace for generated classes; defaults to the package. + optional string csharp_namespace = 37; + + // By default Swift generators will take the proto package and CamelCase it + // replacing '.' with underscore and use that to prefix the types/symbols + // defined. When this options is provided, they will use this value instead + // to prefix the types/symbols defined. + optional string swift_prefix = 39; + + // Sets the php class prefix which is prepended to all php generated classes + // from this .proto. Default is empty. + optional string php_class_prefix = 40; + + // Use this option to change the namespace of php generated classes. Default + // is empty. When this option is empty, the package name will be used for + // determining the namespace. + optional string php_namespace = 41; + + // Use this option to change the namespace of php generated metadata classes. + // Default is empty. When this option is empty, the proto file name will be + // used for determining the namespace. + optional string php_metadata_namespace = 44; + + // Use this option to change the package of ruby generated classes. Default + // is empty. When this option is not set, the package name will be used for + // determining the ruby package. + optional string ruby_package = 45; + + + // The parser stores options it doesn't recognize here. + // See the documentation for the "Options" section above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. + // See the documentation for the "Options" section above. + extensions 1000 to max; + + reserved 38; +} + +message MessageOptions { + // Set true to use the old proto1 MessageSet wire format for extensions. + // This is provided for backwards-compatibility with the MessageSet wire + // format. You should not use this for any other reason: It's less + // efficient, has fewer features, and is more complicated. + // + // The message must be defined exactly as follows: + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // Note that the message cannot have any defined fields; MessageSets only + // have extensions. + // + // All extensions of your type must be singular messages; e.g. they cannot + // be int32s, enums, or repeated messages. + // + // Because this is an option, the above two restrictions are not enforced by + // the protocol compiler. + optional bool message_set_wire_format = 1 [default = false]; + + // Disables the generation of the standard "descriptor()" accessor, which can + // conflict with a field of the same name. This is meant to make migration + // from proto1 easier; new code should avoid fields named "descriptor". + optional bool no_standard_descriptor_accessor = 2 [default = false]; + + // Is this message deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the message, or it will be completely ignored; in the very least, + // this is a formalization for deprecating messages. + optional bool deprecated = 3 [default = false]; + + // Whether the message is an automatically generated map entry type for the + // maps field. + // + // For maps fields: + // map map_field = 1; + // The parsed descriptor looks like: + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; + // + // Implementations may choose not to generate the map_entry=true message, but + // use a native map in the target language to hold the keys and values. + // The reflection APIs in such implementations still need to work as + // if the field is a repeated message field. + // + // NOTE: Do not set the option in .proto files. Always use the maps syntax + // instead. The option should only be implicitly set by the proto compiler + // parser. + optional bool map_entry = 7; + + reserved 8; // javalite_serializable + reserved 9; // javanano_as_lite + + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message FieldOptions { + // The ctype option instructs the C++ code generator to use a different + // representation of the field than it normally would. See the specific + // options below. This option is not yet implemented in the open source + // release -- sorry, we'll try to include it in a future version! + optional CType ctype = 1 [default = STRING]; + enum CType { + // Default mode. + STRING = 0; + + CORD = 1; + + STRING_PIECE = 2; + } + // The packed option can be enabled for repeated primitive fields to enable + // a more efficient representation on the wire. Rather than repeatedly + // writing the tag and type for each element, the entire array is encoded as + // a single length-delimited blob. In proto3, only explicit setting it to + // false will avoid using packed encoding. + optional bool packed = 2; + + // The jstype option determines the JavaScript type used for values of the + // field. The option is permitted only for 64 bit integral and fixed types + // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + // is represented as JavaScript string, which avoids loss of precision that + // can happen when a large value is converted to a floating point JavaScript. + // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + // use the JavaScript "number" type. The behavior of the default option + // JS_NORMAL is implementation dependent. + // + // This option is an enum to permit additional types to be added, e.g. + // goog.math.Integer. + optional JSType jstype = 6 [default = JS_NORMAL]; + enum JSType { + // Use the default type. + JS_NORMAL = 0; + + // Use JavaScript strings. + JS_STRING = 1; + + // Use JavaScript numbers. + JS_NUMBER = 2; + } + + // Should this field be parsed lazily? Lazy applies only to message-type + // fields. It means that when the outer message is initially parsed, the + // inner message's contents will not be parsed but instead stored in encoded + // form. The inner message will actually be parsed when it is first accessed. + // + // This is only a hint. Implementations are free to choose whether to use + // eager or lazy parsing regardless of the value of this option. However, + // setting this option true suggests that the protocol author believes that + // using lazy parsing on this field is worth the additional bookkeeping + // overhead typically needed to implement it. + // + // This option does not affect the public interface of any generated code; + // all method signatures remain the same. Furthermore, thread-safety of the + // interface is not affected by this option; const methods remain safe to + // call from multiple threads concurrently, while non-const methods continue + // to require exclusive access. + // + // + // Note that implementations may choose not to check required fields within + // a lazy sub-message. That is, calling IsInitialized() on the outer message + // may return true even if the inner message has missing required fields. + // This is necessary because otherwise the inner message would have to be + // parsed in order to perform the check, defeating the purpose of lazy + // parsing. An implementation which chooses not to check required fields + // must be consistent about it. That is, for any particular sub-message, the + // implementation must either *always* check its required fields, or *never* + // check its required fields, regardless of whether or not the message has + // been parsed. + optional bool lazy = 5 [default = false]; + + // Is this field deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for accessors, or it will be completely ignored; in the very least, this + // is a formalization for deprecating fields. + optional bool deprecated = 3 [default = false]; + + // For Google-internal migration only. Do not use. + optional bool weak = 10 [default = false]; + + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; + + reserved 4; // removed jtype +} + +message OneofOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumOptions { + + // Set this option to true to allow mapping different tag names to the same + // value. + optional bool allow_alias = 2; + + // Is this enum deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum, or it will be completely ignored; in the very least, this + // is a formalization for deprecating enums. + optional bool deprecated = 3 [default = false]; + + reserved 5; // javanano_as_lite + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumValueOptions { + // Is this enum value deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum value, or it will be completely ignored; in the very least, + // this is a formalization for deprecating enum values. + optional bool deprecated = 1 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message ServiceOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this service deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the service, or it will be completely ignored; in the very least, + // this is a formalization for deprecating services. + optional bool deprecated = 33 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message MethodOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this method deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the method, or it will be completely ignored; in the very least, + // this is a formalization for deprecating methods. + optional bool deprecated = 33 [default = false]; + + // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, + // or neither? HTTP based RPC implementation may choose GET verb for safe + // methods, and PUT verb for idempotent methods instead of the default POST. + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; // implies idempotent + IDEMPOTENT = 2; // idempotent, but may have side effects + } + optional IdempotencyLevel idempotency_level = 34 + [default = IDEMPOTENCY_UNKNOWN]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + + +// A message representing a option the parser does not recognize. This only +// appears in options protos created by the compiler::Parser class. +// DescriptorPool resolves these when building Descriptor objects. Therefore, +// options protos in descriptor objects (e.g. returned by Descriptor::options(), +// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions +// in them. +message UninterpretedOption { + // The name of the uninterpreted option. Each string represents a segment in + // a dot-separated name. is_extension is true iff a segment represents an + // extension (denoted with parentheses in options specs in .proto files). + // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents + // "foo.(bar.baz).qux". + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } + repeated NamePart name = 2; + + // The value of the uninterpreted option, in whatever type the tokenizer + // identified it as during parsing. Exactly one of these should be set. + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; +} + +// =================================================================== +// Optional source code info + +// Encapsulates information about the original source file from which a +// FileDescriptorProto was generated. +message SourceCodeInfo { + // A Location identifies a piece of source code in a .proto file which + // corresponds to a particular definition. This information is intended + // to be useful to IDEs, code indexers, documentation generators, and similar + // tools. + // + // For example, say we have a file like: + // message Foo { + // optional string foo = 1; + // } + // Let's look at just the field definition: + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // We have the following locations: + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // Notes: + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendant. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. + repeated Location location = 1; + message Location { + // Identifies which part of the FileDescriptorProto was defined at this + // location. + // + // Each element is a field number or an index. They form a path from + // the root FileDescriptorProto to the place where the definition. For + // example, this path: + // [ 4, 3, 2, 7, 1 ] + // refers to: + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // This is because FileDescriptorProto.message_type has field number 4: + // repeated DescriptorProto message_type = 4; + // and DescriptorProto.field has field number 2: + // repeated FieldDescriptorProto field = 2; + // and FieldDescriptorProto.name has field number 1: + // optional string name = 1; + // + // Thus, the above path gives the location of a field name. If we removed + // the last element: + // [ 4, 3, 2, 7 ] + // this path refers to the whole field declaration (from the beginning + // of the label to the terminating semicolon). + repeated int32 path = 1 [packed = true]; + + // Always has exactly three or four elements: start line, start column, + // end line (optional, otherwise assumed same as start line), end column. + // These are packed into a single field for efficiency. Note that line + // and column numbers are zero-based -- typically you will want to add + // 1 to each before displaying to a user. + repeated int32 span = 2 [packed = true]; + + // If this SourceCodeInfo represents a complete declaration, these are any + // comments appearing before and after the declaration which appear to be + // attached to the declaration. + // + // A series of line comments appearing on consecutive lines, with no other + // tokens appearing on those lines, will be treated as a single comment. + // + // leading_detached_comments will keep paragraphs of comments that appear + // before (but not connected to) the current element. Each paragraph, + // separated by empty lines, will be one comment element in the repeated + // field. + // + // Only the comment content is provided; comment markers (e.g. //) are + // stripped out. For block comments, leading whitespace and an asterisk + // will be stripped from the beginning of each line other than the first. + // Newlines are included in the output. + // + // Examples: + // + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; + // + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. + // + // // Comment attached to qux. + // // + // // Another line attached to qux. + // optional double qux = 4; + // + // // Detached comment for corge. This is not leading or trailing comments + // // to qux or corge because there are blank lines separating it from + // // both. + // + // // Detached comment for corge paragraph 2. + // + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; + // + // // ignored detached comments. + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } +} + +// Describes the relationship between generated code and its original source +// file. A GeneratedCodeInfo message is associated with only one generated +// source file, but may contain references to different source .proto files. +message GeneratedCodeInfo { + // An Annotation connects some span of text in generated code to an element + // of its generating .proto file. + repeated Annotation annotation = 1; + message Annotation { + // Identifies the element in the original source .proto file. This field + // is formatted the same as SourceCodeInfo.Location.path. + repeated int32 path = 1 [packed = true]; + + // Identifies the filesystem path to the original source .proto. + optional string source_file = 2; + + // Identifies the starting offset in bytes in the generated code + // that relates to the identified object. + optional int32 begin = 3; + + // Identifies the ending offset in bytes in the generated code that + // relates to the identified offset. The end offset should be one past + // the last relevant byte (so the length of the text = end - begin). + optional int32 end = 4; + } +} diff --git a/grpc/server/protobuf/google/protobuf/empty.proto b/grpc/server/protobuf/google/protobuf/empty.proto new file mode 100644 index 0000000000..03cacd2330 --- /dev/null +++ b/grpc/server/protobuf/google/protobuf/empty.proto @@ -0,0 +1,52 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "github.com/golang/protobuf/ptypes/empty"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "EmptyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// A generic empty message that you can re-use to avoid defining duplicated +// empty messages in your APIs. A typical example is to use it as the request +// or the response type of an API method. For instance: +// +// service Foo { +// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); +// } +// +// The JSON representation for `Empty` is empty JSON object `{}`. +message Empty {} diff --git a/grpc/server/protobuf/io/casperlabs/casper/consensus/consensus.proto b/grpc/server/protobuf/io/casperlabs/casper/consensus/consensus.proto new file mode 100644 index 0000000000..b3f9fa341e --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/casper/consensus/consensus.proto @@ -0,0 +1,311 @@ +syntax = "proto3"; +package io.casperlabs.casper.consensus; + +import "io/casperlabs/casper/consensus/state.proto"; + +// Signature over for example a deploy or a block. The location of the public key depends +// on the subject; for example if it's a block then the key is actually part of the data +// that needs to be signed over. +message Signature { + // One of the supported algorithms: ed25519, secp256k1, secp256r1 + string sig_algorithm = 1; + bytes sig = 2; +} + +// A signature together with the corresponding public key that can be used to validate it +// for situations where the key is not part of the data being signed, which is usually the +// case where multiple signatures are required. +message Approval { + bytes approver_public_key = 1; + Signature signature = 2; +} + +// A smart contract invocation, singed by the account that sent it. +message Deploy { + // blake2b256 hash of the `header`. + bytes deploy_hash = 1; + Header header = 2; + Body body = 3; + + // Signatures over `deploy_hash` with either the `account_public_key`, or some other keys in case of + // multi-sig wallets and account recovery (when the private key corresponding to `account_public_key` + // is lost.) + repeated Approval approvals = 4; + + message Header { + // Removed the nonce. + reserved 2; + // Identifying the Account is the key used to sign the Deploy. + bytes account_public_key_hash = 1; + // Current time milliseconds. + uint64 timestamp = 3; + // Conversion rate between the cost of Wasm opcodes and the motes sent by the `payment_code`. + uint64 gas_price = 4; + // Hash of the body structure as a whole. + bytes body_hash = 5; + // Time to live of the deploy, in milliseconds. A deploy can only be + // included in a block between Header.timestamp and + // Header.timestamp + Header.ttl_millis. A value of 0 is interpreted as + // 'not present' and a default value will be assigned instead. + uint32 ttl_millis = 6; + // List of `Deploy.deploy_hash`s that must be executed in past blocks + // before this deploy can be executed. + repeated bytes dependencies = 7; + // If present, the deploy can only be included in a block on the right chain. + // This can be used to preotect against accidental or malicious cross chain + // deploys, in case the same account exists on multiple networks. + string chain_name = 8; + } + + message Body { + // Wasm code of the smart contract to be executed. + Code session = 1; + // Wasm code that transfers some motes to the validators as payment in exchange to run the Deploy. + // Note: the word "mote" means "a tiny piece of a substance". Here we are referring to the smallest, + // indivisible unit of the CL token. This is the equivalent of a Satoshi on Bitcoin or a Wei on Ethereum. + Code payment = 2; + } + + // Code (either session or payment) to be deployed to the platform. + // Includes both binary instructions (wasm) and optionally, arguments + // to those instructions encoded via our ABI + message Code { + // XXX: For the JS serializer the order of fields matters. + // See https://casperlabs.atlassian.net/browse/TNET-45 + + // Keyword arguments. + repeated Arg args = 1; + + oneof contract { + WasmContract wasm_contract = 2; + StoredContract stored_contract = 3; + StoredVersionedContract stored_versioned_contract = 4; + TransferContract transfer_contract = 5; + } + + message WasmContract { + bytes wasm = 1; + } + + message StoredVersionedContract { + oneof address { + // Package hash address of already stored contract. + bytes package_hash = 1; + // Name of a key that contains a package hash. + string name = 2; + } + // Name of the entry method in the contract. + string entry_point = 3; + // Optionally specify the exact version; 0 means use the highest available. + uint32 version = 4; + } + + message StoredContract { + oneof address { + // Public hash address of an already stored contract. + bytes contract_hash = 1; + // Name of a stored contract associated with the executing account (uref or hash). + string name = 2; + } + // Name of the entry method in the contract. + string entry_point = 3; + } + + // Built-in transfer, bypassing the WASM execution. + message TransferContract {} + } + + message Arg { + reserved 2; // old state.Value representation of args + string name = 1; + state.CLValueInstance value = 3; + } + + // Old definition of Arg, still kept around to support old JSON format. + // TODO: delete this when there are no more usages of the old JSON format + message LegacyArg { + string name = 1; + Value value = 2; + message Value { + oneof value { + Value optional_value = 1; + bytes bytes_value = 2; + int32 int_value = 3; + state.IntList int_list = 4; + string string_value = 5; + state.StringList string_list = 6; + int64 long_value = 7; + state.BigInt big_int = 8; + state.Key key = 9; + } + } + } +} + +// Limited deploy information for gossiping. +message DeploySummary { + // blake2b256 hash of the `header`. + bytes deploy_hash = 1; + Deploy.Header header = 2; + // Signatures over `deploy_hash` with either the `account_public_key`, or some other keys in case of + // multi-sig wallets and account recovery (when the private key corresponding to `account_public_key` + // is lost.) + repeated Approval approvals = 3; +} + +// Limited block information for gossiping. +message BlockSummary { + // blake2b256 hash of the `header`. + bytes block_hash = 1; + Block.Header header = 2; + // Signature over `block_hash` using `validator_public_key`. + Signature signature = 3; +} + +// Full block information. +message Block { + // blake2b256 hash of the `header`. + bytes block_hash = 1; + Header header = 2; + Body body = 3; + // Signature over `block_hash`. + Signature signature = 4; + + message Header { + reserved 6; // old u64 protocol version. + repeated bytes parent_hashes = 1; + repeated Justification justifications = 2; + GlobalState state = 3; + // Hash of the body structure as a whole. + bytes body_hash = 4; + // Unix timestamp from when the block was created. + uint64 timestamp = 5; + state.ProtocolVersion protocol_version = 13; + uint32 deploy_count = 7; + string chain_name = 8; + uint32 validator_block_seq_num = 9; + bytes validator_public_key = 10; + bytes validator_public_key_hash = 20; + // The hash of the previous block (or ballot) from this validator. + // It has to be stated even if it's part of the justifications directly, + // and it must be part of the j-past-cone of the block, in case transitive + // justifications are omitted. It must be the one corresponding to + // validator_block_seq_num - 1. + bytes validator_prev_block_hash = 14; + // Distance from Genesis. + // This is a rank based on the justifications of the message. + uint64 j_rank = 11; + MessageType message_type = 12; + MessageRole message_role = 19; + // A block from where the fork choice is calculated. + // Corresponds to the era the block belongs to. + bytes key_block_hash = 15; + // The round ID (the idealistic protocol timestamp) to which the block belongs; + // this will be slightly different from the timestamp, which is the wall clock time + // of when the block was _actually_ created. + uint64 round_id = 16; + // A random bit set by the creator of the block which is goes towards the leader seed + // of the era. Only leader blocks (i.e. lambda messages) have to set it; should be + // empty for ballots. + bool magic_bit = 17; + // Block height. + // Different from `j_rank` because it's incremented along main tree in the DAG, based on the parents. + uint64 main_rank = 18; + } + + enum MessageType { + // Regular block (with deploys, multiple parents etc). + BLOCK = 0; + // A message that doesn't have any deploys, + // targets one block (no secondary parents), + // and cannot be used as a parent (is a leaf in the DAG). + BALLOT = 1; + } + + // Highway specific role of a message. + enum MessageRole { + UNDEFINED = 0; + // Message from the round leader; a.k.a. lambda-message. Can be a block or ballot (during the voting period). + PROPOSAL = 1; + // Message in response to leader messages, with no other justification except the validator's own last message; a.k.a. lambda-response. + CONFIRMATION = 2; + // Message created during the round to form summits, a.k.a. omega-message. + WITNESS = 3; + } + + message Body { + repeated ProcessedDeploy deploys = 1; + } + + message Justification { + bytes validator_public_key_hash = 1; + bytes latest_block_hash = 2; + } + + message ProcessedDeploy { + Deploy deploy = 1; + uint64 cost = 2; + bool is_error = 3; + string error_message = 4; + // A group this deploy will be executed in. + // Deploys with the same `stage` value will be executed in parallel. + uint32 stage = 5; + } + + message GlobalState { + // May not correspond to a particular block if there are multiple parents. + bytes pre_state_hash = 1; + bytes post_state_hash = 2; + // Included in header so lightweight nodes can follow the consensus. + repeated Bond bonds = 3; + } +} + +message Bond { + reserved 2; // Original bond was uint64. + bytes validator_public_key_hash = 1; + state.BigInt stake = 3; +} + + +// To allow nodes in the beginning to verify that they are joining the right network, +// while there is no DAG to look at, they disseminate approvals for the Genesis candidate +// until it gathers enough support for individual nodes to pass their thresholds and +// transition to processing blocks and deploys. +// Nodes should persist this value to be able to retrieve it later even if in case all nodes are restarted. +message GenesisCandidate { + // The hash of the Genesis block, which should reflect the effects of executing + // the block that was compiled according to the published specification. + // The Genesis block is special in that it is not signed by any one validator: + // all the signatures are going to be missing, all the public key fields empty, + // including those that would sign the deploys in the body. + bytes block_hash = 1; + + // Approvals from bonded validators with signatures over the `block_hash`. + repeated Approval approvals = 2; +} + + +message Era { + // Key block of the era, which is basically its identifier. + bytes key_block_hash = 1; + // Identifier of the parent era. + bytes parent_key_block_hash = 2; + // Number of ticks since Unix epoch (in the protocol specific units) when the era starts. + uint64 start_tick = 3; + // Last round of the era (non-inclusive), after which the first block is the switch block. + uint64 end_tick = 4; + // The hash of the booking block which was reachable from the key block; + // this is where the bonds are coming, included in the era for reference. + bytes booking_block_hash = 5; + // The random seed compiled from the magic bits between the booking block and the key block. + // 1. Concatenate all the magic bits into an array of bytes, padding on the right with 0s to make it divisble by 8. + // 2. Concatenate the byte array to the parent leader seed. + // 3. Let the leader_seed be the blake2b256 hash of the resulting array. + // 4. Convert any uint64 tick to bytes in little-endian format. + // 5. Concatentate the the bytes of the tick to the leader_seed and use the array as a seed to a SHA1PRNG generator. + // 6. Generate a random double r between [0, 1) and seek the first validator in `bonds` where the total cumulative weight exceeds r * total. + bytes leader_seed = 6; + // Bonded validator weights from the booking block. + repeated Bond bonds = 7; +} diff --git a/grpc/server/protobuf/io/casperlabs/casper/consensus/info.proto b/grpc/server/protobuf/io/casperlabs/casper/consensus/info.proto new file mode 100644 index 0000000000..92b138077c --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/casper/consensus/info.proto @@ -0,0 +1,161 @@ +syntax = "proto3"; +package io.casperlabs.casper.consensus.info; + +import "io/casperlabs/casper/consensus/consensus.proto"; + +message BlockInfo { + io.casperlabs.casper.consensus.BlockSummary summary = 1; + Status status = 2; + + enum View { + // Only includes information which is based on the header. + BASIC = 0; + // Includes extra information such as children which require extra lookups. + FULL = 1; + } + + message Status { + reserved 1; // fault_tolerance; + reserved 4; // is_finalized; + Stats stats = 2; + repeated bytes child_hashes = 3; + + // Indicate the finality status of the block, i.e. whether it's in the p-past cone + // of the last finalized block. Doesn't apply to ballots, those never get finalized. + Finality finality = 5; + + // Statistics derived from the full block. + message Stats { + uint32 block_size_bytes = 1; + uint32 deploy_error_count = 2; + uint64 deploy_cost_total = 3; + // Average gas price across all deploys in the block weighted by the gas cost of the deploys. + uint64 deploy_gas_price_avg = 4; + } + + enum Finality { + // The fate of the block hasn't been decided yet. + UNDECIDED = 0; + // The block has reached k=1 summit level and has appeared in the finalized block stream. + FINALIZED = 1; + // The block was bypassed by the Last Finalized Block without being finalized, i.e. it will never be finalized now. + ORPHANED = 2; + } + } +} + + +message DeployInfo { + io.casperlabs.casper.consensus.Deploy deploy = 1; + // List of blocks the deploy has been processed in, with results, ordered by newest to oldest. + repeated ProcessingResult processing_results = 2; + Status status = 3; + + message ProcessingResult { + BlockInfo block_info = 1; + uint64 cost = 2; + bool is_error = 3; + string error_message = 4; + uint32 stage = 5; + } + + enum View { + // Only includes the header of the deploys, not the body with the code. + BASIC = 0; + // Includes the body with the code. + FULL = 1; + } + + enum State { + UNDEFINED = 0; + // Waiting to be included in a block. Deploys can go back to pending + // if the block it was included in got orphaned. + PENDING = 1; + // Included in one or more blocks, waiting to be finalized, or potentially + // orphaned and re-queued. + PROCESSED = 2; + // The block the deploy was included has been finalized along with all the deploys in it. + FINALIZED = 3; + // Deploys get discarded if their account doesn't exist, or their TTL expires. + // They will be removed after a grace period to free up space. + DISCARDED = 4; + } + + message Status { + State state = 1; + // Potential explanation for the current state of the deploy, e.g. the reason it got discarded. + string message = 2; + } +} + +message Event { + uint64 event_id = 9; + oneof value { + BlockAdded block_added = 1; + NewFinalizedBlock new_finalized_block = 2; + DeployAdded deploy_added = 3; + DeployRequeued deploy_requeued = 4; + DeployDiscarded deploy_discarded = 5; + DeployProcessed deploy_processed = 6; + DeployFinalized deploy_finalized = 7; + DeployOrphaned deploy_orphaned = 8; + } + + message BlockAdded { + BlockInfo block = 1; + } + + message NewFinalizedBlock { + // Hash of the newly finalized block. + bytes block_hash = 1; + // Set of blocks that were finalized indirectly (secondary parents of the new main-chain LFB). + repeated bytes indirectly_finalized_block_hashes = 2; + // Set of blocks which are never going to be finalized now. + repeated bytes indirectly_orphaned_block_hashes = 3; + } + + // A deploy was added to the local buffer. + message DeployAdded { + io.casperlabs.casper.consensus.Deploy deploy = 1; + } + + // A deploy was discarded from the local buffer either due to a pre-condition error or TTL expiration. + message DeployDiscarded { + io.casperlabs.casper.consensus.Deploy deploy = 1; + string message = 2; + } + + // A deploy has been requeued in the local buffer because none of the blocks it was included in are + // in the p-past cone of the fork choice. Some of the existing blocks may become un-orphaned again if + // the fork choice switches back to their branch. + message DeployRequeued { + io.casperlabs.casper.consensus.Deploy deploy = 1; + } + + // A deploy was executed and included in a block. + message DeployProcessed { + bytes block_hash = 1; + io.casperlabs.casper.consensus.Block.ProcessedDeploy processed_deploy = 2; + } + + // A block the deploy was included in has been finalized. Every `DeployProcessed` event should + // eventually end up being finalized or orphaned (anti-finalized). + message DeployFinalized { + // This block has been finalized directly or indirectly. + bytes block_hash = 1; + // This deploy cannot appear in the descendants of the finalized block as that would be a duplicate, + // so these results are considered final. + io.casperlabs.casper.consensus.Block.ProcessedDeploy processed_deploy = 2; + } + + // A block the deploy was included in has been anti-finalized, i.e. orphaned by the LFB. + // The deploy can still be finalized in a different block, if it was requeued or re-sent. + message DeployOrphaned { + // This block has been passed by the Last Finalized Block and will never be included in the main chain. + bytes block_hash = 1; + // Including the full `DeployInfo `with all the alternative processing results so we can see if there's another, + // finalized block, or something else to wait for. Otherwise the client could pair up Processed and + // Finalized/Anti-finalized events but if they miss something they'd most likely query the status of the deploy anyway. + DeployInfo deploy_info = 2; + } +} diff --git a/grpc/server/protobuf/io/casperlabs/casper/consensus/state.proto b/grpc/server/protobuf/io/casperlabs/casper/consensus/state.proto new file mode 100644 index 0000000000..5629ae2367 --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/casper/consensus/state.proto @@ -0,0 +1,364 @@ +syntax = "proto3"; +package io.casperlabs.casper.consensus.state; + +message SemVer { + uint32 major = 1; + uint32 minor = 2; + uint32 patch = 3; +} + +message Contract { + message EntryPoint { + + message Arg { + string name = 1; + CLType cl_type = 2; + } + message Public {} // Tag + message Group { + string name = 1; + } + + message SessionType {} + message ContractType {} + + message Groups { + repeated Group groups = 1; + } + + string name = 1; + + repeated Arg args = 2; + CLType ret = 3; + oneof access { + Public public = 4; + Groups groups = 5; + } + + + oneof entry_point_type { + SessionType session = 6; + ContractType contract = 7; + } + } + + bytes contract_package_hash = 1; + bytes contract_wasm_hash = 2; + repeated NamedKey named_keys = 3; + repeated EntryPoint entry_points = 4; + ProtocolVersion protocol_version = 6; + +} + +message ContractVersionKey { + uint32 protocol_version_major = 1; + uint32 contract_version = 2; +} + +message ContractPackage { + + message Version { + ContractVersionKey version = 1; + bytes contract_hash = 2; + } + message Group { + Contract.EntryPoint.Group group = 1; + repeated Key.URef urefs = 2; + } + Key.URef access_key = 1; + repeated Version active_versions = 2; + repeated ContractVersionKey disabled_versions = 3; + repeated Group groups = 4; +} + + +// Fundamental type of a user-facing value stored under a key in global state. +message CLType { + enum Simple { + BOOL = 0; + I32 = 1; + I64 = 2; + U8 = 3; + U32 = 4; + U64 = 5; + U128 = 6; + U256 = 7; + U512 = 8; + UNIT = 9; + STRING = 10; + KEY = 11; + UREF = 12; + } + + message Option { + CLType inner = 1; + } + + message List { + CLType inner = 1; + } + + message FixedList { + CLType inner = 1; + uint32 len = 2; + } + + message Result { + CLType ok = 1; + CLType err = 2; + } + + message Map { + CLType key = 1; + CLType value = 2; + } + + message Tuple1 { + CLType type0 = 1; + } + + message Tuple2 { + CLType type0 = 1; + CLType type1 = 2; + } + + message Tuple3 { + CLType type0 = 1; + CLType type1 = 2; + CLType type2 = 3; + } + + message Any {} + + oneof variants { + Simple simple_type = 1; + Option option_type = 2; + List list_type = 3; + FixedList fixed_list_type = 4; + Result result_type = 5; + Map map_type = 6; + Tuple1 tuple1_type = 7; + Tuple2 tuple2_type = 8; + Tuple3 tuple3_type = 9; + Any any_type = 10; + } +} + +// User-facing value stored under a key in global state. +message CLValue { + CLType cl_type = 1; + bytes serialized_value = 2; +} + +// CLValue where the bytes are re-consistituted as an object +message CLValueInstance { + CLType cl_type = 1; + Value value = 2; + + message Value { + oneof value { + bool bool_value = 1; + int32 i32 = 2; + int64 i64 = 3; + // Protobuf does not have an 8-bit primitive, so we use the 32-bit + // primitive and will validate the value is in the range [0, 255]. + int32 u8 = 4; + uint32 u32 = 5; + uint64 u64 = 6; + U128 u128 = 7; + U256 u256 = 8; + U512 u512 = 9; + Unit unit = 10; + string str_value = 11; + Key key = 12; + Key.URef uref = 13; + Option option_value = 14; + List list_value = 15; + FixedList fixed_list_value = 16; + Result result_value = 17; + Map map_value = 18; + Tuple1 tuple1_value = 19; + Tuple2 tuple2_value = 20; + Tuple3 tuple3_value = 21; + bytes bytes_value = 22; // convenience for representing List(U8) / FixedList(U8) + } + } + + // The BigInt types encode their values as strings; it is invalid to have a + // string which cannot be parsed as a non-negative whole number. + message U128 { + string value = 1; + } + message U256 { + string value = 1; + } + message U512 { + string value = 1; + } + message Option { + Value value = 1; + } + // Technically these types can represent heterogeneous lists and maps, + // however it will be considered illegal to do so because there is no way to + // assign a CLType to such an object. + message List { + repeated Value values = 1; + } + message FixedList { + uint32 length = 1; + // length of this list must be equal to length above + repeated Value values = 2; + } + message Result { + oneof value { + Value ok = 1; + Value err = 2; + } + } + message Map { + // key-value pairs + repeated MapEntry values = 1; + } + message MapEntry { + Value key = 1; + Value value = 2; + } + message Tuple1 { + Value value_1 = 1; + } + message Tuple2 { + Value value_1 = 1; + Value value_2 = 2; + } + message Tuple3 { + Value value_1 = 1; + Value value_2 = 2; + Value value_3 = 3; + } + +} + +message ContractWasm { + bytes wasm = 1; +} + +// Value stored under a key in global state. +message StoredValue { + oneof variants { + CLValue cl_value = 1; + Account account = 2; + Contract contract = 3; + ContractPackage contract_package = 4; + ContractWasm contract_wasm = 5; + } +} + +message StoredValueInstance { + oneof value { + CLValueInstance cl_value = 1; + Account account = 2; + Contract contract = 3; + ContractPackage contract_package = 4; + ContractWasm contract_wasm = 5; + } +} + +message Value { + oneof value { + int32 int_value = 1; + bytes bytes_value = 2; + IntList int_list = 3; + string string_value = 4; + Account account = 5; + Contract contract = 6; + StringList string_list = 7; + NamedKey named_key = 8; + BigInt big_int = 9; + Key key = 10; + Unit unit = 11; + uint64 long_value = 12; + } +} + +message IntList { + repeated int32 values = 1; +} + +message StringList { + repeated string values = 1; +} + +message BigInt { + string value = 1; + // Number of bits: 128 | 256 | 512. + uint32 bit_width = 2; +} + +message Key { + oneof value { + Address address = 1; + Hash hash = 2; + URef uref = 3; + } + + message Address { + bytes account = 1; + } + + message Hash { + bytes hash = 1; + } + + message URef { + bytes uref = 1; + AccessRights access_rights = 2; + + // NOTE: Numeric values correspond to values of the domain + // AccessRights struct. DO NOT CHANGE. + enum AccessRights { + NONE = 0; + READ = 1; + WRITE = 2; + ADD = 4; + READ_ADD = 5; + READ_WRITE = 3; + ADD_WRITE = 6; + READ_ADD_WRITE = 7; + } + } +} + +message NamedKey { + string name = 1; + Key key = 2; +} + +message Account { + // Removed: nonce. + reserved 2; + // Removed: account_activity + reserved 7; + + bytes public_key = 1; + Key.URef main_purse = 3; + repeated NamedKey named_keys = 4; + repeated AssociatedKey associated_keys = 5; + ActionThresholds action_thresholds = 6; + + message AssociatedKey { + bytes public_key = 1; + uint32 weight = 2; + } + message ActionThresholds { + uint32 deployment_threshold = 1; + uint32 key_management_threshold = 2; + } +} + +message Unit {} + +message ProtocolVersion { + uint32 major = 1; + uint32 minor = 2; + uint32 patch = 3; +} diff --git a/grpc/server/protobuf/io/casperlabs/comm/discovery/kademlia.proto b/grpc/server/protobuf/io/casperlabs/comm/discovery/kademlia.proto new file mode 100644 index 0000000000..77adb17a98 --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/comm/discovery/kademlia.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; +package io.casperlabs.comm.discovery; + +import "io/casperlabs/comm/discovery/node.proto"; + +service KademliaService { + rpc Ping (PingRequest) returns (PingResponse) {} + rpc Lookup (LookupRequest) returns (LookupResponse) {} +} + +message PingRequest { + Node sender = 1; +} + +message PingResponse {} + +message LookupRequest { + bytes id = 1; + Node sender = 2; +} + +message LookupResponse { + repeated Node nodes = 1; +} \ No newline at end of file diff --git a/grpc/server/protobuf/io/casperlabs/comm/discovery/node.proto b/grpc/server/protobuf/io/casperlabs/comm/discovery/node.proto new file mode 100644 index 0000000000..ccf0d8a729 --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/comm/discovery/node.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package io.casperlabs.comm.discovery; + +message Node { + bytes id = 1; + string host = 2; + uint32 protocol_port = 3; + uint32 discovery_port = 4; + bytes chain_id = 5; + string node_version = 6; +} diff --git a/grpc/server/protobuf/io/casperlabs/comm/gossiping/gossiping.proto b/grpc/server/protobuf/io/casperlabs/comm/gossiping/gossiping.proto new file mode 100644 index 0000000000..f0f18433cd --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/comm/gossiping/gossiping.proto @@ -0,0 +1,151 @@ +syntax = "proto3"; + +package io.casperlabs.comm.gossiping; + +import "google/protobuf/empty.proto"; +import "io/casperlabs/casper/consensus/consensus.proto"; +import "io/casperlabs/comm/discovery/node.proto"; + +service GossipService { + // Notify the callee about new blocks being available on the caller. + rpc NewBlocks(NewBlocksRequest) returns (NewBlocksResponse); + + // Notify the callee about new deploys being available on the caller. + rpc NewDeploys(NewDeploysRequest) returns (NewDeploysResponse); + + // Retrieve the ancestors of certain blocks in the DAG; to be called repeatedly + // as necessary to synchronize DAGs between peers. + rpc StreamAncestorBlockSummaries(StreamAncestorBlockSummariesRequest) returns (stream io.casperlabs.casper.consensus.BlockSummary); + + // Retrieve latest messages as the callee knows them. + rpc StreamLatestMessages(StreamLatestMessagesRequest) returns (stream io.casperlabs.casper.consensus.Block.Justification); + + // Retrieve arbitrary block summaries, if the callee knows about them. + rpc StreamBlockSummaries(StreamBlockSummariesRequest) returns (stream io.casperlabs.casper.consensus.BlockSummary); + + // Retrieve arbitrary deploy summaries, if the callee knows about them. + rpc StreamDeploySummaries(StreamDeploySummariesRequest) returns (stream io.casperlabs.casper.consensus.DeploySummary); + + // Retrieve an arbitrarily sized block as a stream of chunks, with optionally compressed content. + rpc GetBlockChunked(GetBlockChunkedRequest) returns (stream Chunk); + + // Retrieve an arbitrary list of deploys, with optionally compressed contents. + // One use case would be to gossip deploys between nodes so that they can be included + // in a block by the first validator who gets to propose a block. The second case is + // to retrieve all the deploys which are missing from the body of a block, i.e. haven't + // been downloaded yet as part of earlier blocks or deploy gossiping. + rpc StreamDeploysChunked(StreamDeploysChunkedRequest) returns (stream Chunk); + + // Retrieve the Genesis candidate supported by this node. Every node should either produce one from specification, + // get one from its bootstrap, or have it persisted from earlier runs. + // While the node is initializing and it hasn't obtained the candidate yet it will return UNAVAILABLE. + // Once the node is serving a candidate identity, it should also be prepared to serve the full block on request. + rpc GetGenesisCandidate(GetGenesisCandidateRequest) returns (io.casperlabs.casper.consensus.GenesisCandidate); + + // Add a signature to the list of approvals of a given candidate. If the block hash in the request doesn't match + // the callee's candidate it will return INVALID_ARGUMENT, otherwise add the signature and forward it to its peers. + rpc AddApproval(AddApprovalRequest) returns (google.protobuf.Empty); + + // Returns block summaries between two ranks (inclusive) in ascending topological order. + // Used to gradually perform initial synchronization for new peers in the network. + rpc StreamDagSliceBlockSummaries(StreamDagSliceBlockSummariesRequest) returns (stream io.casperlabs.casper.consensus.BlockSummary); +} + +message NewBlocksRequest { + // Address of the caller from where the callee can download the blocks from. + io.casperlabs.comm.discovery.Node sender = 1; + repeated bytes block_hashes = 2; +} + +message NewBlocksResponse { + // Indicate that some of the blocks in the notifications were new and the callee will attempt + // to gossip them to further nodes when it gets around to downloading them. + bool is_new = 1; +} + +message NewDeploysRequest { + // Address of the caller from where the callee can download the deploys from. + io.casperlabs.comm.discovery.Node sender = 1; + repeated bytes deploy_hashes = 2; +} + +message NewDeploysResponse { + // Indicate that some of the deploys in the notifications were new and the callee will attempt + // to gossip them to further nodes when it gets around to downloading them. + bool is_new = 1; +} + +message StreamBlockSummariesRequest { + repeated bytes block_hashes = 1; +} + +message StreamDeploySummariesRequest { + repeated bytes deploy_hashes = 1; +} + +message StreamAncestorBlockSummariesRequest { + // The identifiers of the blocks the caller wants to get to, typically the ones the callee notified + // it about earlier, using `NewBlocks`. + repeated bytes target_block_hashes = 1; + + // Supply the callee with some block hashes already known to the caller so the traversal can stop early if it hits them. + // These can for example be the blocks last seen from each validator and the last finalized blocks. + repeated bytes known_block_hashes = 2; + + // Limit the traversal to a certain depth, as a checkpoint when the caller can reassess and ask for + // any hashes that still couldn't be connected to its own version of the DAG. If the target block + // summaries are known then the value can be based on the difference in block sequence numbers between + // the target and the last known block from the validator that produced it. + // A value of -1 would mean no limit on the depth; 0 just returns the targets themselves. + uint32 max_depth = 3; +} + +message StreamLatestMessagesRequest {} + +message GetBlockChunkedRequest { + bytes block_hash = 1; + uint32 chunk_size = 2; + repeated string accepted_compression_algorithms = 3; + // Download the processed deploys without their bodies. + bool exclude_deploy_bodies = 4; + // Download the processed deploys with only the hash, no body, header and approvals. + bool only_include_deploy_hashes = 5; +} + +message StreamDeploysChunkedRequest { + repeated bytes deploy_hashes = 1; + uint32 chunk_size = 2; + repeated string accepted_compression_algorithms = 3; +} + +message GetGenesisCandidateRequest {} + +message AddApprovalRequest { + // Hash of the candidate the caller supports. + bytes block_hash = 1; + io.casperlabs.casper.consensus.Approval approval = 2; +} + +// Generic message for transferring a stream of data that wouldn't fit into single gRPC messages. +message Chunk { + // Alternating between a header and subsequent chunks of data. + oneof content { + Header header = 1; + bytes data = 2; + } + + message Header { + // Indicate if compression was used on the data. e.g. lz4 + string compression_algorithm = 1; + // Use the `content_length` to sanity check the size of the data in the chunks that follow. + uint32 content_length = 2; + // The original content length before any compression was applied. + uint32 original_content_length = 3; + } +} + +message StreamDagSliceBlockSummariesRequest { + uint64 start_rank = 1; + // Inclusive + uint64 end_rank = 2; +} diff --git a/grpc/server/protobuf/io/casperlabs/ipc/ipc.proto b/grpc/server/protobuf/io/casperlabs/ipc/ipc.proto new file mode 100644 index 0000000000..3b404060a3 --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/ipc/ipc.proto @@ -0,0 +1,504 @@ +syntax = "proto3"; + +package io.casperlabs.ipc; + +import "io/casperlabs/casper/consensus/state.proto"; +import "io/casperlabs/ipc/transforms.proto"; + +// --- BEGIN EXECUTION ENGINE SERVICE DEFINITION --- // + +message DeployCode { + bytes code = 1; // wasm byte code + bytes args = 2; // ABI-encoded arguments +} + +message StoredContractHash{ + bytes hash = 1; // public hash of a stored contract + bytes args = 2; // ABI-encoded arguments + string entry_point_name = 3; // optional entry point name (defaults to "call") +} + +message StoredContractName{ + // name of a stored contract associated with the executing account + string name = 1; + bytes args = 2; // ABI-encoded arguments + string entry_point_name = 3; // optional entry point name (defaults to "call") +} + +message StoredContractPackage { + string name = 1; // key name + oneof optional_version { + uint32 version = 2; // optional specific version (defaults to highest) + } + string entry_point_name = 3; // finds header by entrypoint name + bytes args = 4; // ABI-encoded arguments +} + +message StoredContractPackageHash { + bytes hash = 1; // public hash of a stored contract package + oneof optional_version { + uint32 version = 2; // finds active version + } + string entry_point_name = 3; // finds header by entrypoint name + bytes args = 4; // ABI-encoded arguments +} + +message Transfer { + bytes args = 1; // ABI-encoded arguments +} + +message DeployPayload { + reserved 4; // StoredContractURef is no longer supported + oneof payload { + DeployCode deploy_code = 1; + StoredContractHash stored_contract_hash = 2; + StoredContractName stored_contract_name = 3; + StoredContractPackage stored_package_by_name = 5; + StoredContractPackageHash stored_package_by_hash = 6; + Transfer transfer = 7; + } +} + +message Bond { + bytes validator_account_hash = 1; + io.casperlabs.casper.consensus.state.BigInt stake = 2; +} + +message DeployItem { + reserved 5; // motes in payment + reserved 7; // nonce + // Public key hash of the account which is the context of the execution. + bytes address = 1; // length 32 bytes + DeployPayload session = 3; + DeployPayload payment = 4; + uint64 gas_price = 6; // in units of Mote / Gas + // Hashes of the public keys used to sign this deploy, to be checked against the keys + // associated with the account. + repeated bytes authorization_keys = 8; + bytes deploy_hash = 9; +} + +message ExecuteRequest { + bytes parent_state_hash = 1; + uint64 block_time = 2; + repeated DeployItem deploys = 3; + io.casperlabs.casper.consensus.state.ProtocolVersion protocol_version = 4; +} + +message ExecuteResponse { + oneof result { + ExecResult success = 1; + RootNotFound missing_parent = 2; + } +} + +message ExecResult { + repeated DeployResult deploy_results = 2; +} + +message RootNotFound { + bytes hash = 1; +} + +message CommitRequest { + bytes prestate_hash = 1; + repeated TransformEntry effects = 2; + io.casperlabs.casper.consensus.state.ProtocolVersion protocol_version = 3; +} + +message CommitResult { + bytes poststate_hash = 1; + repeated Bond bonded_validators = 2; +} + +message CommitResponse { + oneof result { + CommitResult success = 1; + RootNotFound missing_prestate = 2; + io.casperlabs.casper.consensus.state.Key key_not_found = 3; + TypeMismatch type_mismatch = 4; + PostEffectsError failed_transform = 5; + } +} + +// Describes operation that are allowed to do on a value under a key. +message Op { + oneof op_instance { + ReadOp read = 1; + WriteOp write = 2; + AddOp add = 3; + NoOp noop = 4; + } +} +message ReadOp {} +message WriteOp {} +message AddOp {} +message NoOp {} + + +//Errors which may occur while interacting with global state +message StorageError { + oneof error_instance { + BytesReprError bytes_repr = 1; + RkvError rkv = 2; + } +} + +message BytesReprError { + oneof error_instance { + EarlyEndOfStream early_end = 1; + FormattingError formatting = 2; + LeftOverBytes left_over = 3; + } +} +message EarlyEndOfStream {} +message FormattingError {} +message LeftOverBytes {} +message RkvError { + string error_msg = 1; +} + +// Models key value pair of (key, op) entry. +// Required b/c protobuff doesn't support maps natively +message OpEntry { + io.casperlabs.casper.consensus.state.Key key = 1; + Op operation = 2; +} + +// Returned by ExecutionEngine to consensus layer. +// (Map[Key, Op], Map[Key, Transform]) pair, describes how the deploy modifies the global io.casperlabs.casper.consensus.state. +// op_map and transform_map should be of equal lengths +message ExecutionEffect { + repeated OpEntry op_map = 1; + repeated TransformEntry transform_map = 2; +} + +message DeployError { + // Run out of gas during contract execution. + message OutOfGasError {} + + // Error during contract execution. + message ExecutionError { + string message = 1; + } + + oneof value { + OutOfGasError gas_error = 1; + ExecutionError exec_error = 2; + } +} + +message DeployResult { + // Deploys that failed because of precondition failure that we can't charge for + // (invalid key format, invalid key address, invalid Wasm deploys). + message PreconditionFailure { + string message = 1; + } + + // Execution result has effects and/or errors. + // Failed execution mutates the GlobalState by paying for the deploy. + message ExecutionResult { + ExecutionEffect effects = 1; + DeployError error = 2; + io.casperlabs.casper.consensus.state.BigInt cost = 3; + } + + oneof value { + PreconditionFailure precondition_failure = 2; + ExecutionResult execution_result = 3; + } + +} + +//TODO: be more specific about errors +message PostEffectsError { + string message = 1; +} + +message QueryRequest { + bytes state_hash = 1; + io.casperlabs.casper.consensus.state.Key base_key = 2; + repeated string path = 3; + io.casperlabs.casper.consensus.state.ProtocolVersion protocol_version = 4; +} + +message QueryResponse { + reserved 1; // previously `state.Value` + oneof result { + // serialized `StoredValue` + bytes success = 3; + //TODO: ADT for errors + string failure = 2; + } +} + + +message GenesisResult { + bytes poststate_hash = 1; + ExecutionEffect effect = 2; +} + +message GenesisDeployError { + string message = 1; +} + +message GenesisResponse { + oneof result { + GenesisResult success = 1; + GenesisDeployError failed_deploy = 2; + } +} + +message ChainSpec { + GenesisConfig genesis = 1; + repeated UpgradePoint upgrades = 2; + + message GenesisConfig { + reserved 4; // mint_installer + reserved 5; // pos_installer + reserved 10; // standard_payment_installer + reserved 6; // genesis accounts + reserved 7; // WASM costs table + // Human readable name for convenience; the genesis_hash is the true identifier. + // The name influences the genesis hash by contributing to the seeding of the pseudo- + // random number generator used in execution engine for computing genesis post-state. + string name = 1; + // timestamp for the genesis block, also used in seeding the pseudo-random number + // generator used in execution engine for computing genesis post-state. + uint64 timestamp = 2; + io.casperlabs.casper.consensus.state.ProtocolVersion protocol_version = 3; + DeployConfig deploy_config = 8; + HighwayConfig highway_config = 9; + ExecConfig ee_config = 11; + + message ExecConfig { + // wasm bytecode for installing the mint system contract + bytes mint_installer = 1; + // wasm bytes for installing the pos system contract + bytes pos_installer = 2; + // wasm bytes for installing the standard payment system contract + bytes standard_payment_installer = 3; + // genesis account information + repeated GenesisAccount accounts = 4; + // costs at genesis + CostTable costs = 5; + + message GenesisAccount { + bytes public_key_hash = 1; + io.casperlabs.casper.consensus.state.BigInt balance = 2; // in motes + io.casperlabs.casper.consensus.state.BigInt bonded_amount = 3; // in motes, 0 means "not bonded" + } + } + } + + message DeployConfig { + uint32 max_ttl_millis = 2; + uint32 max_dependencies = 3; + uint32 max_block_size_bytes = 4; + uint64 max_block_cost = 5; + } + + message HighwayConfig { + // Unix timestamp of the Genesis era start. + uint64 genesis_era_start_timestamp = 1; + // Fixed length duration of an era. + uint64 era_duration_millis = 2; + // Amount of time to look back from the start of an era to pick the booking block. + uint64 booking_duration_millis = 3; + // Amount of time after the booking time to allow for magic bits to accumulate before picking the key block. + uint64 entropy_duration_millis = 4; + // Fixed length duration of the post-era voting period; used when summit level is zero. + uint64 voting_period_duration_millis = 5; + // Stop the post-era voting period when the switch block reaches a certain summit level; when 0 use the duration instead. + uint32 voting_period_summit_level = 6; + // Relative fault tolerance threshold value used by the internal finalizer. + // Has to be between 0 and 0.5 . + double ftt = 7; + + } + + message CostTable { + WasmCosts wasm = 1; + // TODO (https://casperlabs.atlassian.net/browse/EE-638): design host function costs + + message WasmCosts { + // Default opcode cost + uint32 regular = 1; + // Div operations multiplier. + uint32 div = 2; + // Mul operations multiplier. + uint32 mul = 3; + // Memory (load/store) operations multiplier. + uint32 mem = 4; + // Amount of free memory (in 64kb pages) each contract can use for stack. + uint32 initial_mem = 5; + // Grow memory cost, per page (64kb) + uint32 grow_mem = 6; + // Memory copy cost, per byte + uint32 memcpy = 7; + // Max stack height (native WebAssembly stack limiter) + uint32 max_stack_height = 8; + // Cost of wasm opcode is calculated as TABLE_ENTRY_COST * `opcodes_mul` / `opcodes_div` + uint32 opcodes_mul = 9; + uint32 opcodes_div = 10; + } + } + + message UpgradePoint { + // Hiding this behind an abstraction so we are free + // to change how such a point is expressed in the future. + ActivationPoint activation_point = 1; + // The protocol version as of this upgrade + io.casperlabs.casper.consensus.state.ProtocolVersion protocol_version = 2; + // bytes for a contract to run that applies the upgrades to system contracts + DeployCode upgrade_installer = 3; + // Note: this is optional; only needed when costs are changing + CostTable new_costs = 4; + DeployConfig new_deploy_config = 5; + } + + message ActivationPoint { + // equal to Block.Header.rank + uint64 rank = 1; + } +} + +message UpgradeRequest { + bytes parent_state_hash = 1; + ChainSpec.UpgradePoint upgrade_point = 2; + io.casperlabs.casper.consensus.state.ProtocolVersion protocol_version = 3; +} + +message UpgradeResult { + bytes post_state_hash = 1; + ExecutionEffect effect = 2; +} + +message UpgradeDeployError { + string message = 1; +} + +message UpgradeResponse { + oneof result { + UpgradeResult success = 1; + UpgradeDeployError failed_deploy = 2; + } +} + +// --- END EXECUTION ENGINE SERVICE DEFINITION --- // + +// --- BEGIN PROOF-OF-STAKE SERVICE DEFINITION --- // + +message BidStateRequest { + bytes parent_state_hash = 1; + io.casperlabs.casper.consensus.state.ProtocolVersion protocol_version = 2; +} + +message BidState { + repeated Bid bids = 1; + + message Bid { + bytes id = 1; + io.casperlabs.casper.consensus.state.BigInt value = 2; + } +} + +message BidStateResponse { + oneof result { + BidState success = 1; + RootNotFound missing_parent = 2; + } +} + +message DistributeRewardsRequest { + bytes parent_state_hash = 1; + repeated ValidatorReward rewards = 2; + io.casperlabs.casper.consensus.state.ProtocolVersion protocol_version = 3; + + message ValidatorReward { + bytes validator_id = 1; + io.casperlabs.casper.consensus.state.BigInt value = 2; + } +} + +message DistibuteRewardsError { + string message = 1; // TODO: enum of possible errors +} + +message DistributeRewardsResponse { + oneof result { + // effects of rewards distribution are committed automatically, so commit result is returned in the success case + CommitResult success = 1; + RootNotFound missing_parent = 2; + DistibuteRewardsError error = 3; + } +} + +message SlashRequest { + bytes parent_state_hash = 1; + repeated ValidatorSlash slashes = 2; + io.casperlabs.casper.consensus.state.ProtocolVersion protocol_version = 3; + + message ValidatorSlash{ + bytes validator_id = 1; + io.casperlabs.casper.consensus.state.BigInt value = 2; + } +} + +message SlashError { + string message = 1; // TODO: enum of possible errors +} + +message SlashResponse { + oneof result { + // effects of slashing are committed automatically, so commit result is returned in the success case + CommitResult success = 1; + RootNotFound missing_parent = 2; + SlashError error = 3; + } +} + +message UnbondPayoutRequest { + bytes parent_state_hash = 1; + uint64 era_height = 2; + io.casperlabs.casper.consensus.state.ProtocolVersion protocol_version = 3; +} + +message UnbondPayoutError { + string message = 1; // TODO: enum of possible errors +} + +message UnbondPayoutResponse { + oneof result { + // effects of unbond payment are committed automatically, so commit result is returned in the success case + CommitResult success = 1; + RootNotFound missing_parent = 2; + UnbondPayoutError error = 3; + } +} + +message RunGenesisRequest { + // Hash of the Genesis configuration. + bytes genesis_config_hash = 1; + io.casperlabs.casper.consensus.state.ProtocolVersion protocol_version = 2; + // Genesis configuration for the ExecutionEngine. + ChainSpec.GenesisConfig.ExecConfig ee_config = 3; +} + +// --- END PROOF-OF-STAKE SERVICE DEFINITION --- // + +// Definition of the service. +// ExecutionEngine implements server part while Consensus implements client part. +service ExecutionEngineService { + // execution endpoints + rpc commit (CommitRequest) returns (CommitResponse) {} + rpc query (QueryRequest) returns (QueryResponse) {} + rpc execute (ExecuteRequest) returns (ExecuteResponse) {} + rpc run_genesis (RunGenesisRequest) returns (GenesisResponse) {} + rpc upgrade (UpgradeRequest) returns (UpgradeResponse) {} + // proof-of-stake endpoints + rpc bid_state(BidStateRequest) returns (BidStateResponse) {} + rpc distribute_rewards(DistributeRewardsRequest) returns (DistributeRewardsResponse) {} + rpc slash(SlashRequest) returns (SlashResponse) {} + rpc unbond_payout(UnbondPayoutRequest) returns (UnbondPayoutResponse) {} +} diff --git a/grpc/server/protobuf/io/casperlabs/ipc/transforms.proto b/grpc/server/protobuf/io/casperlabs/ipc/transforms.proto new file mode 100644 index 0000000000..2bad05f57d --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/ipc/transforms.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +package io.casperlabs.ipc; + +import "io/casperlabs/casper/consensus/state.proto"; + +message TypeMismatch { + string expected = 1; + string found = 2; +} + +// Final transformation to the value under the key. +// It's the outcome of applying all `op`s +message Transform { + oneof transform_instance { + TransformIdentity identity = 1; + TransformAddInt32 add_i32 = 2; + TransformAddUInt64 add_u64 = 3; + TransformWrite write = 4; + TransformAddKeys add_keys = 5; + TransformFailure failure = 6; + TransformAddBigInt add_big_int = 7; + } +} + +message TransformIdentity {} +message TransformAddInt32 { + int32 value = 1; +} +message TransformAddUInt64 { + uint64 value = 1; +} +message TransformAddBigInt { + io.casperlabs.casper.consensus.state.BigInt value = 1; +} +message TransformAddKeys { + repeated io.casperlabs.casper.consensus.state.NamedKey value = 1; +} +message TransformWrite { + io.casperlabs.casper.consensus.state.StoredValue value = 1; +} +message TransformFailure { + oneof failure_instance { + TypeMismatch type_mismatch = 1; + } +} + +message TransformEntry { + io.casperlabs.casper.consensus.state.Key key = 1; + Transform transform = 2; +} \ No newline at end of file diff --git a/grpc/server/protobuf/io/casperlabs/node/api/casper.proto b/grpc/server/protobuf/io/casperlabs/node/api/casper.proto new file mode 100644 index 0000000000..397e2dbc1f --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/node/api/casper.proto @@ -0,0 +1,209 @@ +syntax = "proto3"; + +package io.casperlabs.node.api.casper; + +import "google/api/annotations.proto"; +import "google/protobuf/empty.proto"; +import "io/casperlabs/casper/consensus/consensus.proto"; +import "io/casperlabs/casper/consensus/info.proto"; +import "io/casperlabs/casper/consensus/state.proto"; + +// CasperService is the way for user and dApp developer to interact with the system, +// including deploying contracts, looking at the DAG and querying state. +service CasperService { + + // Add a deploy to the deploy pool on the node, + // to be processed during subsequent block proposals. + rpc Deploy(DeployRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/deploys" + body: "deploy" + }; + } + + // Get the block summary with extra information about finality. + rpc GetBlockInfo(GetBlockInfoRequest) returns (io.casperlabs.casper.consensus.info.BlockInfo) { + option (google.api.http) = { + get: "/v2/blocks/{block_hash_base16=*}" + }; + } + + // Get slices of the DAG, going backwards, rank by rank. + rpc StreamBlockInfos(StreamBlockInfosRequest) returns (stream io.casperlabs.casper.consensus.info.BlockInfo) { + option (google.api.http) = { + get: "/v2/blocks" + }; + } + + // Retrieve information about a single deploy by hash. + rpc GetDeployInfo(GetDeployInfoRequest) returns (io.casperlabs.casper.consensus.info.DeployInfo) { + option (google.api.http) = { + get: "/v2/deploys/{deploy_hash_base16=*}" + }; + } + + // Get the processed deploys within a block. + rpc StreamBlockDeploys(StreamBlockDeploysRequest) returns (stream io.casperlabs.casper.consensus.Block.ProcessedDeploy) { + option (google.api.http) = { + get: "/v2/blocks{block_hash_base16=*}/deploys" + }; + } + + rpc StreamEvents(StreamEventsRequest) returns (stream io.casperlabs.casper.consensus.info.Event){ + option (google.api.http) = { + get: "/v2/events" + }; + } + + // Query the value of global state as it was after the execution of a block. + rpc GetBlockState(GetBlockStateRequest) returns (io.casperlabs.casper.consensus.state.StoredValueInstance) { + option (google.api.http) = { + post: "v2/blocks/{block_hash_base16=*}/state" + body: "*" + }; + } + + // Execute multiple state queries at once. + rpc BatchGetBlockState(BatchGetBlockStateRequest) returns (BatchGetBlockStateResponse) { + option (google.api.http) = { + post: "v2/blocks/{block_hash_base16=*}/state:batchGet" + body: "*" + }; + } + + // Get deploys list of a given account + rpc ListDeployInfos (ListDeployInfosRequest) returns (ListDeployInfosResponse) { + option (google.api.http) = { + get: "v2/accounts/{account_public_key_hash_base16=*}/deploys" + }; + } + + rpc GetLastFinalizedBlockInfo (GetLastFinalizedBlockInfoRequest) returns (io.casperlabs.casper.consensus.info.BlockInfo) { + option (google.api.http) = { + get: "v2/last-finalized-block" + }; + } +} + +message DeployRequest { + io.casperlabs.casper.consensus.Deploy deploy = 1; +} + +message GetBlockInfoRequest { + // Either the full or just the first few characters of the block hash in base16 encoding, + // so that it works with the client's redacted displays. + string block_hash_base16 = 1; + // Alternative to the base16 encoded value (which is intended for URL compatibility). + bytes block_hash = 3; + io.casperlabs.casper.consensus.info.BlockInfo.View view = 2; +} + +message StreamBlockInfosRequest { + // How many of the top ranks of the DAG to show. 0 will not return anything. + uint32 depth = 1; + + // Optionally specify the maximum rank to to go back from. + // 0 means go from the current tip of the DAG. + uint64 max_rank = 2; + + io.casperlabs.casper.consensus.info.BlockInfo.View view = 3; +} + + + +message GetDeployInfoRequest { + string deploy_hash_base16 = 1; + // Alternative to the base16 encoded value (which is intended for URL compatibility). + bytes deploy_hash = 3; + io.casperlabs.casper.consensus.info.DeployInfo.View view = 2; +} + +message StreamBlockDeploysRequest { + string block_hash_base16 = 1; + // Alternative to the base16 encoded value (which is intended for URL compatibility). + bytes block_hash = 3; + io.casperlabs.casper.consensus.info.DeployInfo.View view = 2; +} + +message StateQuery { + KeyVariant key_variant = 1; + string key_base16 = 2; + // Path of human readable names to the value. + repeated string path_segments = 3; + + enum KeyVariant { + KEY_VARIANT_UNSPECIFIED = 0; + HASH = 1; + UREF = 2; + ADDRESS = 3; + LOCAL = 4; + } +} + +message GetBlockStateRequest { + string block_hash_base16 = 1; + // Alternative to the base16 encoded value (which is intended for URL compatibility). + bytes block_hash = 3; + StateQuery query = 2; +} + +message BatchGetBlockStateRequest { + string block_hash_base16 = 1; + // Alternative to the base16 encoded value (which is intended for URL compatibility). + bytes block_hash = 3; + repeated StateQuery queries = 2; +} + +message BatchGetBlockStateResponse { + reserved 1; // previously repeated io.casperlabs.casper.consensus.state.Value + repeated io.casperlabs.casper.consensus.state.StoredValueInstance values = 2; +} + +message ListDeployInfosRequest { + string account_public_key_hash_base16 = 1; + // Alternative to the base16 encoded value (which is intended for URL compatibility). + bytes account_public_key_hash = 5; + io.casperlabs.casper.consensus.info.DeployInfo.View view = 2; + uint32 page_size = 3; + string page_token = 4; +} + +message ListDeployInfosResponse { + repeated io.casperlabs.casper.consensus.info.DeployInfo deploy_infos = 1; + string next_page_token = 2; + string prev_page_token = 3; +} + + +message GetLastFinalizedBlockInfoRequest { + io.casperlabs.casper.consensus.info.BlockInfo.View view = 1; +} + +message StreamEventsRequest { + bool block_added = 1; + bool block_finalized = 2; + bool deploy_added = 3; + bool deploy_discarded = 4; + bool deploy_requeued = 5; + bool deploy_processed = 6; + bool deploy_finalized = 7; + bool deploy_orphaned = 8; + + // Optional filters for deploy events; applies to anything opted into via the flags. + DeployFilter deploy_filter = 9; + + // Supports replaying events from a given ID. + // If the value is 0, it it will subscribe to future events; + // if it's non-zero, it will replay all past events from that ID, without subscribing to new. + // To catch up with events from the beginning, start from 1. + uint64 min_event_id = 10; + uint64 max_event_id = 11; + + // Filters to apply on deploy events; different fields combined with AND operator. + message DeployFilter { + // Filter to any of the accounts on the list. + repeated bytes account_public_key_hashes = 1; + // Filter for specific deploy hashes. + repeated bytes deploy_hashes = 2; + } +} diff --git a/grpc/server/protobuf/io/casperlabs/node/api/control.proto b/grpc/server/protobuf/io/casperlabs/node/api/control.proto new file mode 100644 index 0000000000..46125d22f9 --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/node/api/control.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package io.casperlabs.node.api.control; + +import "google/api/annotations.proto"; + +// The operators can use the ControlService to issue commands to the node, +// such as proposing a block or otherwise influence the proposal strategy. +service ControlService { + // Propose a block using the deploys in the pool. + rpc Propose(ProposeRequest) returns (ProposeResponse) { + option (google.api.http) = { + post: "/v2:propose" + body: "*" + }; + } +} + +message ProposeRequest {} + +message ProposeResponse { + bytes block_hash = 1; +} diff --git a/grpc/server/protobuf/io/casperlabs/node/api/diagnostics.proto b/grpc/server/protobuf/io/casperlabs/node/api/diagnostics.proto new file mode 100644 index 0000000000..1312959a35 --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/node/api/diagnostics.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package io.casperlabs.node.api.diagnostics; + +import "google/protobuf/empty.proto"; +import "io/casperlabs/comm/discovery/node.proto"; + +message NodeCoreMetrics { + int64 pingReceiverCount = 1; + int64 lookupReceiverCount = 2; + int64 disconnectReceiverCount = 3; + int64 connects = 4; + int64 p2pEncryptionHandshakeReceiverCount = 5; + int64 p2pProtocolHandshakeReceiverCount = 6; + int64 peers = 7; + int64 from = 8; + int64 to = 9; +} + +service Diagnostics { + rpc ListPeers (google.protobuf.Empty) returns (Peers); +} + +message Peers { + repeated io.casperlabs.comm.discovery.Node peers = 1; +} diff --git a/grpc/server/protobuf/io/casperlabs/storage/storage.proto b/grpc/server/protobuf/io/casperlabs/storage/storage.proto new file mode 100644 index 0000000000..6787edacf1 --- /dev/null +++ b/grpc/server/protobuf/io/casperlabs/storage/storage.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; + +package io.casperlabs.storage; + +import "io/casperlabs/casper/consensus/consensus.proto"; +import "io/casperlabs/ipc/transforms.proto"; +import "scalapb/scalapb.proto"; + +// This is the content storing in BlockStorage, the reason why we need it is that, +// we take protobuf as the way to (de)serializer. +message BlockMsgWithTransform { + reserved 2; + io.casperlabs.casper.consensus.Block block_message = 1; + repeated StageEffects block_effects = 3; + + message StageEffects { + uint32 stage = 1; + repeated io.casperlabs.ipc.TransformEntry effects = 2; + } +} + +// Storage type for the DagStorage +message BlockMetadataInternal { + // This message in mapped to a different Scala class because of protobuf's inability to create map for + // bonds. + option (scalapb.message).type = "io.casperlabs.models.BlockMetadata"; + + bytes blockHash = 1; + repeated bytes parents = 2 [(scalapb.field).collection_type="collection.immutable.List"]; + bytes validator_public_key = 3; + repeated io.casperlabs.casper.consensus.Block.Justification justifications = 4 [(scalapb.field).collection_type="collection.immutable.List"]; + repeated io.casperlabs.casper.consensus.Bond bonds = 5 [(scalapb.field).collection_type="collection.immutable.List"]; + int64 rank = 6; + int32 validator_block_seq_num = 7; +} diff --git a/grpc/server/src/engine_server/mappings/ipc/bond.rs b/grpc/server/src/engine_server/mappings/ipc/bond.rs new file mode 100644 index 0000000000..d1e56176e3 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/bond.rs @@ -0,0 +1,47 @@ +use std::convert::{TryFrom, TryInto}; + +use types::{account::AccountHash, U512}; + +use crate::engine_server::{ipc::Bond, mappings::MappingError}; + +impl From<(AccountHash, U512)> for Bond { + fn from((account_hash, amount): (AccountHash, U512)) -> Self { + let mut pb_bond = Bond::new(); + pb_bond.validator_account_hash = account_hash.as_bytes().to_vec(); + pb_bond.set_stake(amount.into()); + pb_bond + } +} + +impl TryFrom for (AccountHash, U512) { + type Error = MappingError; + + fn try_from(mut pb_bond: Bond) -> Result { + // TODO: our TryFromSliceForAccountHashError should convey length info + let account_hash = + AccountHash::try_from(pb_bond.get_validator_account_hash()).map_err(|_| { + MappingError::invalid_account_hash_length(pb_bond.validator_account_hash.len()) + })?; + + let stake = pb_bond.take_stake().try_into()?; + + Ok((account_hash, stake)) + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use types::gens; + + use super::*; + use crate::engine_server::mappings::test_utils; + + proptest! { + #[test] + fn round_trip(account_hash in gens::account_hash_arb(), u512 in gens::u512_arb()) { + test_utils::protobuf_round_trip::<(AccountHash, U512), Bond>((account_hash, u512)); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/deploy_item.rs b/grpc/server/src/engine_server/mappings/ipc/deploy_item.rs new file mode 100644 index 0000000000..a1dc0a147f --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/deploy_item.rs @@ -0,0 +1,73 @@ +use std::{ + collections::BTreeSet, + convert::{TryFrom, TryInto}, +}; + +use node::contract_core::engine_state::deploy_item::DeployItem; +use types::account::AccountHash; + +use crate::engine_server::{ipc, mappings::MappingError}; + +impl TryFrom for DeployItem { + type Error = MappingError; + + fn try_from(mut pb_deploy_item: ipc::DeployItem) -> Result { + let address = AccountHash::try_from(pb_deploy_item.get_address()) + .map_err(|_| MappingError::invalid_account_hash_length(pb_deploy_item.address.len()))?; + + let session = pb_deploy_item + .take_session() + .payload + .ok_or_else(|| MappingError::MissingPayload)? + .try_into()?; + + let payment = pb_deploy_item + .take_payment() + .payload + .ok_or_else(|| MappingError::MissingPayload)? + .try_into()?; + + let gas_price = pb_deploy_item.get_gas_price(); + + let authorization_keys = pb_deploy_item + .get_authorization_keys() + .iter() + .map(|raw: &Vec| { + AccountHash::try_from(raw.as_slice()) + .map_err(|_| MappingError::invalid_account_hash_length(raw.len())) + }) + .collect::, Self::Error>>()?; + + let deploy_hash = pb_deploy_item.get_deploy_hash().try_into().map_err(|_| { + MappingError::invalid_deploy_hash_length(pb_deploy_item.deploy_hash.len()) + })?; + + Ok(DeployItem::new( + address, + session, + payment, + gas_price, + authorization_keys, + deploy_hash, + )) + } +} + +impl From for ipc::DeployItem { + fn from(deploy_item: DeployItem) -> Self { + let mut result = ipc::DeployItem::new(); + result.set_address(deploy_item.address.as_bytes().to_vec()); + result.set_session(deploy_item.session.into()); + result.set_payment(deploy_item.payment.into()); + result.set_gas_price(deploy_item.gas_price); + result.set_authorization_keys( + deploy_item + .authorization_keys + .into_iter() + .map(|key| key.as_bytes().to_vec()) + .collect(), + ); + result.set_deploy_hash(deploy_item.deploy_hash.to_vec()); + result + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/deploy_result.rs b/grpc/server/src/engine_server/mappings/ipc/deploy_result.rs new file mode 100644 index 0000000000..0b3df5b009 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/deploy_result.rs @@ -0,0 +1,256 @@ +use node::contract_core::{ + engine_state::{ + execution_effect::ExecutionEffect, execution_result::ExecutionResult, + Error as EngineStateError, + }, + execution::Error as ExecutionError, +}; +use node::contract_shared::gas::Gas; + +use crate::engine_server::ipc::{DeployError_OutOfGasError, DeployResult}; + +impl From for DeployResult { + fn from(execution_result: ExecutionResult) -> DeployResult { + match execution_result { + ExecutionResult::Success { effect, cost } => detail::execution_success(effect, cost), + ExecutionResult::Failure { + error, + effect, + cost, + } => (error, effect, cost).into(), + } + } +} + +impl From<(EngineStateError, ExecutionEffect, Gas)> for DeployResult { + fn from((engine_state_error, effect, cost): (EngineStateError, ExecutionEffect, Gas)) -> Self { + match engine_state_error { + // TODO(mateusz.gorski): Fix error model for the storage errors. + // We don't have separate IPC messages for storage errors so for the time being they are + // all reported as "wasm errors". + error @ EngineStateError::InvalidHashLength { .. } + | error @ EngineStateError::InvalidAccountHashLength { .. } + | error @ EngineStateError::InvalidProtocolVersion { .. } + | error @ EngineStateError::InvalidUpgradeConfig + | error @ EngineStateError::WasmPreprocessing(_) + | error @ EngineStateError::WasmSerialization(_) + | error @ EngineStateError::Exec(ExecutionError::DeploymentAuthorizationFailure) + | error @ EngineStateError::InvalidKeyVariant(_) + | error @ EngineStateError::Authorization + | error @ EngineStateError::InvalidDeployItemVariant(_) + | error @ EngineStateError::InvalidUpgradeResult => { + detail::precondition_error(error.to_string()) + } + EngineStateError::Storage(storage_error) => { + detail::execution_error(storage_error, effect, cost) + } + EngineStateError::MissingSystemContract(msg) => { + detail::execution_error(msg, effect, cost) + } + error @ EngineStateError::InsufficientPayment + | error @ EngineStateError::Deploy + | error @ EngineStateError::Finalization + | error @ EngineStateError::Serialization(_) + | error @ EngineStateError::Mint(_) => detail::execution_error(error, effect, cost), + EngineStateError::Exec(exec_error) => (exec_error, effect, cost).into(), + } + } +} + +impl From<(ExecutionError, ExecutionEffect, Gas)> for DeployResult { + fn from((exec_error, effect, cost): (ExecutionError, ExecutionEffect, Gas)) -> Self { + match exec_error { + ExecutionError::GasLimit => detail::out_of_gas_error(effect, cost), + ExecutionError::KeyNotFound(key) => { + detail::execution_error(format!("Key {:?} not found.", key), effect, cost) + } + ExecutionError::Revert(status) => { + detail::execution_error(status.to_string(), effect, cost) + } + ExecutionError::Interpreter(error) => detail::execution_error(error, effect, cost), + // TODO(mateusz.gorski): Be more specific about execution errors + other => detail::execution_error(format!("{:?}", other), effect, cost), + } + } +} + +mod detail { + use super::{DeployError_OutOfGasError, DeployResult, ExecutionEffect, Gas}; + + /// Constructs an instance of `DeployResult` with no error set, i.e. a successful + /// result. + pub(super) fn execution_success(effect: ExecutionEffect, cost: Gas) -> DeployResult { + deploy_result(DeployErrorType::None, effect, cost) + } + + /// Constructs an instance of `DeployResult` with an error set to + /// `ProtobufPreconditionFailure`. + pub(super) fn precondition_error(msg: String) -> DeployResult { + let mut pb_deploy_result = DeployResult::new(); + pb_deploy_result.mut_precondition_failure().set_message(msg); + pb_deploy_result + } + + /// Constructs an instance of `DeployResult` with an error set to + /// `ProtobufExecutionError`. + pub(super) fn execution_error( + msg: T, + effect: ExecutionEffect, + cost: Gas, + ) -> DeployResult { + deploy_result(DeployErrorType::Exec(msg.to_string()), effect, cost) + } + + /// Constructs an instance of `DeployResult` with an error set to + /// `DeployError_OutOfGasError`. + pub(super) fn out_of_gas_error(effect: ExecutionEffect, cost: Gas) -> DeployResult { + deploy_result(DeployErrorType::OutOfGas, effect, cost) + } + + enum DeployErrorType { + None, + OutOfGas, + Exec(String), + } + + /// Constructs an instance of `DeployResult` with an error set to + /// `DeployError_OutOfGasError` or `ProtobufExecutionError` or with no error set, depending on + /// the value of `error_type`. + fn deploy_result( + error_type: DeployErrorType, + effect: ExecutionEffect, + cost: Gas, + ) -> DeployResult { + let mut pb_deploy_result = DeployResult::new(); + + let pb_execution_result = pb_deploy_result.mut_execution_result(); + match error_type { + DeployErrorType::None => (), + DeployErrorType::OutOfGas => pb_execution_result + .mut_error() + .set_gas_error(DeployError_OutOfGasError::new()), + DeployErrorType::Exec(msg) => pb_execution_result + .mut_error() + .mut_exec_error() + .set_message(msg), + } + pb_execution_result.set_effects(effect.into()); + pb_execution_result.set_cost(cost.value().into()); + + pb_deploy_result + } +} + +#[cfg(test)] +mod tests { + use std::convert::TryInto; + + use node::contract_shared::{additive_map::AdditiveMap, transform::Transform}; + use types::{bytesrepr::Error as BytesReprError, AccessRights, ApiError, Key, URef, U512}; + + use super::*; + + #[test] + fn deploy_result_to_ipc_success() { + let input_transforms: AdditiveMap = { + let mut tmp_map = AdditiveMap::new(); + tmp_map.insert( + Key::URef(URef::new([1u8; 32], AccessRights::ADD)), + Transform::AddInt32(10), + ); + tmp_map + }; + let execution_effect = ExecutionEffect::new(AdditiveMap::new(), input_transforms.clone()); + let cost = Gas::new(U512::from(123)); + let execution_result = ExecutionResult::Success { + effect: execution_effect, + cost, + }; + let mut ipc_deploy_result: DeployResult = execution_result.into(); + assert!(ipc_deploy_result.has_execution_result()); + let mut success = ipc_deploy_result.take_execution_result(); + let execution_cost: U512 = success.take_cost().try_into().expect("should map to U512"); + assert_eq!(execution_cost, cost.value()); + + // Extract transform map from the IPC message and parse it back to the domain + let ipc_transforms: AdditiveMap = { + let mut ipc_effects = success.take_effects(); + let ipc_effects_tnfs = ipc_effects.take_transform_map().into_vec(); + ipc_effects_tnfs + .into_iter() + .map(TryInto::try_into) + .collect::, _>>() + .unwrap() + }; + assert_eq!(input_transforms, ipc_transforms); + } + + fn test_cost>(expected_cost: Gas, error: E) -> Gas { + let execution_failure = ExecutionResult::Failure { + error: error.into(), + effect: Default::default(), + cost: expected_cost, + }; + let mut ipc_deploy_result: DeployResult = execution_failure.into(); + assert!(ipc_deploy_result.has_execution_result()); + let execution_result = ipc_deploy_result.mut_execution_result(); + let execution_cost: U512 = execution_result + .take_cost() + .try_into() + .expect("should map to U512"); + Gas::new(execution_cost) + } + + #[test] + fn storage_error_has_cost() { + let cost = Gas::new(U512::from(100)); + // TODO: actually create an Rkv error + // assert_eq!(test_cost(cost, RkvError("Error".to_owned())), cost); + let bytesrepr_err = BytesReprError::EarlyEndOfStream; + assert_eq!( + test_cost(cost, ExecutionError::BytesRepr(bytesrepr_err)), + cost + ); + } + + #[test] + fn exec_err_has_cost() { + let cost = Gas::new(U512::from(100)); + // GasLimit error is treated differently at the moment so test separately + assert_eq!(test_cost(cost, ExecutionError::GasLimit), cost); + // for the time being all other execution errors are treated in the same way + let forged_ref_error = + ExecutionError::ForgedReference(URef::new([1u8; 32], AccessRights::READ_ADD_WRITE)); + assert_eq!(test_cost(cost, forged_ref_error), cost); + } + + #[test] + fn revert_error_maps_to_execution_error() { + let expected_revert = ApiError::UnexpectedContractRefVariant; + let revert_error = ExecutionError::Revert(expected_revert); + let amount = U512::from(15); + let exec_result = ExecutionResult::Failure { + error: EngineStateError::Exec(revert_error), + effect: Default::default(), + cost: Gas::new(amount), + }; + let mut ipc_result: DeployResult = exec_result.into(); + assert!( + ipc_result.has_execution_result(), + "should have execution result" + ); + let ipc_execution_result = ipc_result.mut_execution_result(); + let execution_cost: U512 = ipc_execution_result + .take_cost() + .try_into() + .expect("should map to U512"); + assert_eq!(execution_cost, amount, "execution cost should equal amount"); + assert_eq!( + ipc_execution_result + .get_error() + .get_exec_error() + .get_message(), + expected_revert.to_string(), + ); + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/exec_config.rs b/grpc/server/src/engine_server/mappings/ipc/exec_config.rs new file mode 100644 index 0000000000..df5ae3437f --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/exec_config.rs @@ -0,0 +1,68 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_core::engine_state::genesis::{ExecConfig, GenesisAccount}; + +use crate::engine_server::{ipc, mappings::MappingError}; + +impl TryFrom for ExecConfig { + type Error = MappingError; + + fn try_from( + mut pb_exec_config: ipc::ChainSpec_GenesisConfig_ExecConfig, + ) -> Result { + let accounts = pb_exec_config + .take_accounts() + .into_iter() + .map(TryInto::try_into) + .collect::, Self::Error>>()?; + let wasm_costs = pb_exec_config.take_costs().take_wasm().into(); + let mint_initializer_bytes = pb_exec_config.take_mint_installer(); + let proof_of_stake_initializer_bytes = pb_exec_config.take_pos_installer(); + let standard_payment_installer_bytes = pb_exec_config.take_standard_payment_installer(); + Ok(ExecConfig::new( + mint_initializer_bytes, + proof_of_stake_initializer_bytes, + standard_payment_installer_bytes, + accounts, + wasm_costs, + )) + } +} + +impl From for ipc::ChainSpec_GenesisConfig_ExecConfig { + fn from(exec_config: ExecConfig) -> ipc::ChainSpec_GenesisConfig_ExecConfig { + let mut pb_exec_config = ipc::ChainSpec_GenesisConfig_ExecConfig::new(); + pb_exec_config.set_mint_installer(exec_config.mint_installer_bytes().to_vec()); + pb_exec_config.set_pos_installer(exec_config.proof_of_stake_installer_bytes().to_vec()); + pb_exec_config.set_standard_payment_installer( + exec_config.standard_payment_installer_bytes().to_vec(), + ); + { + let accounts = exec_config + .accounts() + .iter() + .cloned() + .map(Into::into) + .collect::>(); + pb_exec_config.set_accounts(accounts.into()); + } + pb_exec_config + .mut_costs() + .set_wasm(exec_config.wasm_costs().into()); + pb_exec_config + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::engine_server::mappings::test_utils; + + #[test] + fn round_trip() { + let exec_config = rand::random(); + test_utils::protobuf_round_trip::( + exec_config, + ); + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/executable_deploy_item.rs b/grpc/server/src/engine_server/mappings/ipc/executable_deploy_item.rs new file mode 100644 index 0000000000..4e0a8e6c7c --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/executable_deploy_item.rs @@ -0,0 +1,140 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_core::engine_state::executable_deploy_item::ExecutableDeployItem; + +use crate::engine_server::{ + ipc::{DeployPayload, DeployPayload_oneof_payload}, + mappings::MappingError, +}; + +impl TryFrom for ExecutableDeployItem { + type Error = MappingError; + fn try_from(pb_deploy_payload: DeployPayload_oneof_payload) -> Result { + Ok(match pb_deploy_payload { + DeployPayload_oneof_payload::deploy_code(pb_deploy_code) => { + ExecutableDeployItem::ModuleBytes { + module_bytes: pb_deploy_code.code, + args: pb_deploy_code.args, + } + } + DeployPayload_oneof_payload::stored_contract_hash(pb_stored_contract_hash) => { + let mut contract_hash = [0u8; 32]; + contract_hash.copy_from_slice(&pb_stored_contract_hash.hash); + ExecutableDeployItem::StoredContractByHash { + hash: contract_hash, + entry_point: pb_stored_contract_hash.entry_point_name, + args: pb_stored_contract_hash.args, + } + } + DeployPayload_oneof_payload::stored_contract_name(pb_stored_contract_name) => { + ExecutableDeployItem::StoredContractByName { + name: pb_stored_contract_name.name, + entry_point: pb_stored_contract_name.entry_point_name, + args: pb_stored_contract_name.args, + } + } + DeployPayload_oneof_payload::stored_package_by_name(mut pb_stored_package_by_name) => { + ExecutableDeployItem::StoredVersionedContractByName { + name: pb_stored_package_by_name.take_name(), + version: if pb_stored_package_by_name.has_version() + && pb_stored_package_by_name.get_version() > 0 + { + Some(pb_stored_package_by_name.get_version()) + } else { + None + }, + entry_point: pb_stored_package_by_name.entry_point_name, + args: pb_stored_package_by_name.args, + } + } + DeployPayload_oneof_payload::stored_package_by_hash(mut pb_stored_package_by_hash) => { + let hash_bytes = pb_stored_package_by_hash.take_hash(); + let hash = hash_bytes + .as_slice() + .try_into() + .map_err(|_| MappingError::invalid_hash_length(hash_bytes.len()))?; + ExecutableDeployItem::StoredVersionedContractByHash { + hash, + version: if pb_stored_package_by_hash.has_version() + && pb_stored_package_by_hash.get_version() > 0 + { + Some(pb_stored_package_by_hash.get_version()) + } else { + None + }, + entry_point: pb_stored_package_by_hash.entry_point_name, + args: pb_stored_package_by_hash.args, + } + } + DeployPayload_oneof_payload::transfer(pb_transfer) => ExecutableDeployItem::Transfer { + args: pb_transfer.args, + }, + }) + } +} + +impl From for DeployPayload { + fn from(edi: ExecutableDeployItem) -> Self { + let mut result = DeployPayload::new(); + match edi { + ExecutableDeployItem::ModuleBytes { module_bytes, args } => { + let code = result.mut_deploy_code(); + code.set_code(module_bytes); + code.set_args(args); + } + ExecutableDeployItem::StoredContractByHash { + hash, + entry_point, + args, + } => { + let inner = result.mut_stored_contract_hash(); + inner.set_hash(hash.to_vec()); + inner.set_entry_point_name(entry_point); + inner.set_args(args); + } + ExecutableDeployItem::StoredContractByName { + name, + entry_point, + args, + } => { + let inner = result.mut_stored_contract_name(); + inner.set_name(name); + inner.set_entry_point_name(entry_point); + inner.set_args(args); + } + ExecutableDeployItem::StoredVersionedContractByName { + name, + version, + entry_point, + args, + } => { + let inner = result.mut_stored_package_by_name(); + inner.set_name(name); + if let Some(ver) = version { + inner.set_version(ver) + } + inner.set_entry_point_name(entry_point); + inner.set_args(args); + } + ExecutableDeployItem::StoredVersionedContractByHash { + hash, + version, + entry_point, + args, + } => { + let inner = result.mut_stored_package_by_hash(); + inner.set_hash(hash.to_vec()); + if let Some(ver) = version { + inner.set_version(ver) + } + inner.set_entry_point_name(entry_point); + inner.set_args(args); + } + ExecutableDeployItem::Transfer { args } => { + let inner = result.mut_transfer(); + inner.set_args(args); + } + } + result + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/execute_request.rs b/grpc/server/src/engine_server/mappings/ipc/execute_request.rs new file mode 100644 index 0000000000..160d474724 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/execute_request.rs @@ -0,0 +1,70 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_core::engine_state::{ + execute_request::ExecuteRequest, execution_result::ExecutionResult, +}; +use node::contract_shared::newtypes::BLAKE2B_DIGEST_LENGTH; + +use crate::engine_server::{ipc, mappings::MappingError}; + +impl TryFrom for ExecuteRequest { + type Error = ipc::ExecuteResponse; + + fn try_from(mut request: ipc::ExecuteRequest) -> Result { + let parent_state_hash = { + let parent_state_hash = request.take_parent_state_hash(); + let length = parent_state_hash.len(); + if length != BLAKE2B_DIGEST_LENGTH { + let mut result = ipc::ExecuteResponse::new(); + result.mut_missing_parent().set_hash(parent_state_hash); + return Err(result); + } + parent_state_hash.as_slice().try_into().map_err(|_| { + let mut result = ipc::ExecuteResponse::new(); + result + .mut_missing_parent() + .set_hash(parent_state_hash.clone()); + result + })? + }; + + let block_time = request.get_block_time(); + + let deploys = Into::>::into(request.take_deploys()) + .into_iter() + .map(|deploy_item| { + deploy_item + .try_into() + .map_err(|err: MappingError| ExecutionResult::precondition_failure(err.into())) + }) + .collect(); + + let protocol_version = request.take_protocol_version().into(); + + Ok(ExecuteRequest::new( + parent_state_hash, + block_time, + deploys, + protocol_version, + )) + } +} + +impl From for ipc::ExecuteRequest { + fn from(req: ExecuteRequest) -> Self { + let mut result = ipc::ExecuteRequest::new(); + result.set_parent_state_hash(req.parent_state_hash.to_vec()); + result.set_block_time(req.block_time); + result.set_deploys( + req.deploys + .into_iter() + .map(|res| match res { + Ok(deploy_item) => deploy_item.into(), + Err(_) => ipc::DeployItem::new(), + }) + .collect(), + ); + result.set_protocol_version(req.protocol_version.into()); + result + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/execution_effect.rs b/grpc/server/src/engine_server/mappings/ipc/execution_effect.rs new file mode 100644 index 0000000000..eaa5217360 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/execution_effect.rs @@ -0,0 +1,42 @@ +use node::contract_core::engine_state::{execution_effect::ExecutionEffect, op::Op}; +use types::Key; + +use crate::engine_server::{ + ipc::{self, AddOp, NoOp, OpEntry, ReadOp, WriteOp}, + transforms::TransformEntry as ProbufTransformEntry, +}; + +impl From<(Key, Op)> for OpEntry { + fn from((key, op): (Key, Op)) -> OpEntry { + let mut pb_op_entry = OpEntry::new(); + + pb_op_entry.set_key(key.into()); + + match op { + Op::Read => pb_op_entry.mut_operation().set_read(ReadOp::new()), + Op::Write => pb_op_entry.mut_operation().set_write(WriteOp::new()), + Op::Add => pb_op_entry.mut_operation().set_add(AddOp::new()), + Op::NoOp => pb_op_entry.mut_operation().set_noop(NoOp::new()), + }; + + pb_op_entry + } +} + +impl From for ipc::ExecutionEffect { + fn from(execution_effect: ExecutionEffect) -> ipc::ExecutionEffect { + let mut pb_execution_effect = ipc::ExecutionEffect::new(); + + let pb_op_map: Vec = execution_effect.ops.into_iter().map(Into::into).collect(); + pb_execution_effect.set_op_map(pb_op_map.into()); + + let pb_transform_map: Vec = execution_effect + .transforms + .into_iter() + .map(Into::into) + .collect(); + pb_execution_effect.set_transform_map(pb_transform_map.into()); + + pb_execution_effect + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/genesis_account.rs b/grpc/server/src/engine_server/mappings/ipc/genesis_account.rs new file mode 100644 index 0000000000..e5ecfbc393 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/genesis_account.rs @@ -0,0 +1,59 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_core::engine_state::genesis::GenesisAccount; +use node::contract_shared::motes::Motes; +use types::account::AccountHash; + +use crate::engine_server::{ + ipc::ChainSpec_GenesisConfig_ExecConfig_GenesisAccount, mappings::MappingError, +}; + +impl From for ChainSpec_GenesisConfig_ExecConfig_GenesisAccount { + fn from(genesis_account: GenesisAccount) -> Self { + let mut pb_genesis_account = ChainSpec_GenesisConfig_ExecConfig_GenesisAccount::new(); + + pb_genesis_account.public_key_hash = genesis_account.account_hash().as_bytes().to_vec(); + pb_genesis_account.set_balance(genesis_account.balance().value().into()); + pb_genesis_account.set_bonded_amount(genesis_account.bonded_amount().value().into()); + + pb_genesis_account + } +} + +impl TryFrom for GenesisAccount { + type Error = MappingError; + + fn try_from( + mut pb_genesis_account: ChainSpec_GenesisConfig_ExecConfig_GenesisAccount, + ) -> Result { + // TODO: our TryFromSliceForAccountHashError should convey length info + let account_hash = AccountHash::try_from(&pb_genesis_account.public_key_hash as &[u8]) + .map_err(|_| { + MappingError::invalid_account_hash_length(pb_genesis_account.public_key_hash.len()) + })?; + let balance = pb_genesis_account + .take_balance() + .try_into() + .map(Motes::new)?; + let bonded_amount = pb_genesis_account + .take_bonded_amount() + .try_into() + .map(Motes::new)?; + Ok(GenesisAccount::new(account_hash, balance, bonded_amount)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::engine_server::mappings::test_utils; + + #[test] + fn round_trip() { + let genesis_account = rand::random(); + test_utils::protobuf_round_trip::< + GenesisAccount, + ChainSpec_GenesisConfig_ExecConfig_GenesisAccount, + >(genesis_account); + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/genesis_config.rs b/grpc/server/src/engine_server/mappings/ipc/genesis_config.rs new file mode 100644 index 0000000000..3bbd5343e2 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/genesis_config.rs @@ -0,0 +1,46 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_core::engine_state::genesis::GenesisConfig; + +use crate::engine_server::{ipc::ChainSpec_GenesisConfig, mappings::MappingError}; + +impl From for ChainSpec_GenesisConfig { + fn from(genesis_config: GenesisConfig) -> Self { + let mut pb_genesis_config = ChainSpec_GenesisConfig::new(); + + pb_genesis_config.set_name(genesis_config.name().to_string()); + pb_genesis_config.set_timestamp(genesis_config.timestamp()); + pb_genesis_config.set_protocol_version(genesis_config.protocol_version().into()); + pb_genesis_config.set_ee_config(genesis_config.take_ee_config().into()); + pb_genesis_config + } +} + +impl TryFrom for GenesisConfig { + type Error = MappingError; + + fn try_from(mut pb_genesis_config: ChainSpec_GenesisConfig) -> Result { + let name = pb_genesis_config.take_name(); + let timestamp = pb_genesis_config.get_timestamp(); + let protocol_version = pb_genesis_config.take_protocol_version().into(); + let ee_config = pb_genesis_config.take_ee_config().try_into()?; + Ok(GenesisConfig::new( + name, + timestamp, + protocol_version, + ee_config, + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::engine_server::mappings::test_utils; + + #[test] + fn round_trip() { + let genesis_config = rand::random(); + test_utils::protobuf_round_trip::(genesis_config); + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/mod.rs b/grpc/server/src/engine_server/mappings/ipc/mod.rs new file mode 100644 index 0000000000..93f061a5de --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/mod.rs @@ -0,0 +1,16 @@ +//! Functions for converting between CasperLabs types and their Protobuf equivalents which are +//! defined in protobuf/io/casperlabs/ipc/ipc.proto + +mod bond; +mod deploy_item; +mod deploy_result; +mod exec_config; +mod executable_deploy_item; +mod execute_request; +mod execution_effect; +mod genesis_account; +mod genesis_config; +mod query_request; +mod run_genesis_request; +mod upgrade_request; +mod wasm_costs; diff --git a/grpc/server/src/engine_server/mappings/ipc/query_request.rs b/grpc/server/src/engine_server/mappings/ipc/query_request.rs new file mode 100644 index 0000000000..2989865f5c --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/query_request.rs @@ -0,0 +1,35 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_core::engine_state::query::QueryRequest; +use node::contract_shared::newtypes::BLAKE2B_DIGEST_LENGTH; + +use crate::engine_server::{ipc, mappings::MappingError}; + +impl TryFrom for QueryRequest { + type Error = MappingError; + + fn try_from(mut query_request: ipc::QueryRequest) -> Result { + let state_hash = { + let state_hash = query_request.get_state_hash(); + let length = state_hash.len(); + if length != BLAKE2B_DIGEST_LENGTH { + return Err(MappingError::InvalidStateHashLength { + expected: BLAKE2B_DIGEST_LENGTH, + actual: length, + }); + } + state_hash + .try_into() + .map_err(|_| MappingError::TryFromSlice)? + }; + + let key = query_request + .take_base_key() + .try_into() + .map_err(MappingError::Parsing)?; + + let path = query_request.take_path().into_vec(); + + Ok(QueryRequest::new(state_hash, key, path)) + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/run_genesis_request.rs b/grpc/server/src/engine_server/mappings/ipc/run_genesis_request.rs new file mode 100644 index 0000000000..5663746ae1 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/run_genesis_request.rs @@ -0,0 +1,45 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_core::engine_state::run_genesis_request::RunGenesisRequest; + +use crate::engine_server::{ipc, mappings::MappingError}; + +impl TryFrom for RunGenesisRequest { + type Error = MappingError; + + fn try_from(mut run_genesis_request: ipc::RunGenesisRequest) -> Result { + let hash: [u8; 32] = run_genesis_request + .get_genesis_config_hash() + .try_into() + .map_err(|_| MappingError::TryFromSlice)?; + Ok(RunGenesisRequest::new( + hash.into(), + run_genesis_request.take_protocol_version().into(), + run_genesis_request.take_ee_config().try_into()?, + )) + } +} + +impl From for ipc::RunGenesisRequest { + fn from(run_genesis_request: RunGenesisRequest) -> ipc::RunGenesisRequest { + let mut res = ipc::RunGenesisRequest::new(); + res.set_genesis_config_hash(run_genesis_request.genesis_config_hash().value().to_vec()); + res.set_protocol_version(run_genesis_request.protocol_version().into()); + res.set_ee_config(run_genesis_request.take_ee_config().into()); + res + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::engine_server::mappings::test_utils; + + #[test] + fn round_trip() { + let run_genesis_request = rand::random(); + test_utils::protobuf_round_trip::( + run_genesis_request, + ); + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/upgrade_request.rs b/grpc/server/src/engine_server/mappings/ipc/upgrade_request.rs new file mode 100644 index 0000000000..e269498a43 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/upgrade_request.rs @@ -0,0 +1,54 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_core::engine_state::upgrade::UpgradeConfig; +use types::ProtocolVersion; + +use crate::engine_server::{ipc::UpgradeRequest, mappings::MappingError}; + +impl TryFrom for UpgradeConfig { + type Error = MappingError; + + fn try_from(mut pb_upgrade_request: UpgradeRequest) -> Result { + let pre_state_hash = pb_upgrade_request + .get_parent_state_hash() + .try_into() + .map_err(|_| MappingError::InvalidStateHash("pre_state_hash".to_string()))?; + + let current_protocol_version = pb_upgrade_request.take_protocol_version().into(); + + let upgrade_point = pb_upgrade_request.mut_upgrade_point(); + let new_protocol_version: ProtocolVersion = upgrade_point.take_protocol_version().into(); + let (upgrade_installer_bytes, upgrade_installer_args) = + if !upgrade_point.has_upgrade_installer() { + (None, None) + } else { + let upgrade_installer = upgrade_point.take_upgrade_installer(); + let bytes = upgrade_installer.code; + let bytes = if bytes.is_empty() { None } else { Some(bytes) }; + let args = upgrade_installer.args; + let args = if args.is_empty() { None } else { Some(args) }; + (bytes, args) + }; + + let wasm_costs = if !upgrade_point.has_new_costs() { + None + } else { + Some(upgrade_point.mut_new_costs().take_wasm().into()) + }; + let activation_point = if !upgrade_point.has_activation_point() { + None + } else { + Some(upgrade_point.get_activation_point().rank) + }; + + Ok(UpgradeConfig::new( + pre_state_hash, + current_protocol_version, + new_protocol_version, + upgrade_installer_args, + upgrade_installer_bytes, + wasm_costs, + activation_point, + )) + } +} diff --git a/grpc/server/src/engine_server/mappings/ipc/wasm_costs.rs b/grpc/server/src/engine_server/mappings/ipc/wasm_costs.rs new file mode 100644 index 0000000000..33c3e6c067 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/ipc/wasm_costs.rs @@ -0,0 +1,55 @@ +use node::contract_shared::wasm_costs::WasmCosts; + +use crate::engine_server::ipc::ChainSpec_CostTable_WasmCosts; + +impl From for ChainSpec_CostTable_WasmCosts { + fn from(wasm_costs: WasmCosts) -> Self { + ChainSpec_CostTable_WasmCosts { + regular: wasm_costs.regular, + div: wasm_costs.div, + mul: wasm_costs.mul, + mem: wasm_costs.mem, + initial_mem: wasm_costs.initial_mem, + grow_mem: wasm_costs.grow_mem, + memcpy: wasm_costs.memcpy, + max_stack_height: wasm_costs.max_stack_height, + opcodes_mul: wasm_costs.opcodes_mul, + opcodes_div: wasm_costs.opcodes_div, + ..Default::default() + } + } +} + +impl From for WasmCosts { + fn from(pb_wasm_costs: ChainSpec_CostTable_WasmCosts) -> Self { + WasmCosts { + regular: pb_wasm_costs.regular, + div: pb_wasm_costs.div, + mul: pb_wasm_costs.mul, + mem: pb_wasm_costs.mem, + initial_mem: pb_wasm_costs.initial_mem, + grow_mem: pb_wasm_costs.grow_mem, + memcpy: pb_wasm_costs.memcpy, + max_stack_height: pb_wasm_costs.max_stack_height, + opcodes_mul: pb_wasm_costs.opcodes_mul, + opcodes_div: pb_wasm_costs.opcodes_div, + } + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use node::contract_shared::wasm_costs::gens; + + use super::*; + use crate::engine_server::mappings::test_utils; + + proptest! { + #[test] + fn round_trip(wasm_costs in gens::wasm_costs_arb()) { + test_utils::protobuf_round_trip::(wasm_costs); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/mod.rs b/grpc/server/src/engine_server/mappings/mod.rs new file mode 100644 index 0000000000..64a2cec590 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/mod.rs @@ -0,0 +1,148 @@ +//! Functions for converting between CasperLabs types and their Protobuf equivalents. + +mod ipc; +mod state; +mod transforms; + +use std::{ + convert::TryInto, + fmt::{self, Display, Formatter}, + string::ToString, +}; + +use node::contract_core::{engine_state, DEPLOY_HASH_LENGTH}; +use types::{account::ACCOUNT_HASH_LENGTH, KEY_HASH_LENGTH}; + +pub use transforms::TransformMap; + +/// Try to convert a `Vec` to a 32-byte array. +pub(crate) fn vec_to_array(input: Vec, input_name: &str) -> Result<[u8; 32], ParsingError> { + input + .as_slice() + .try_into() + .map_err(|_| format!("{} must be 32 bytes.", input_name).into()) +} + +#[derive(Debug)] +pub enum MappingError { + InvalidStateHashLength { expected: usize, actual: usize }, + InvalidAccountHashLength { expected: usize, actual: usize }, + InvalidDeployHashLength { expected: usize, actual: usize }, + InvalidHashLength { expected: usize, actual: usize }, + Parsing(ParsingError), + InvalidStateHash(String), + MissingPayload, + TryFromSlice, +} + +impl MappingError { + pub fn invalid_account_hash_length(actual: usize) -> Self { + let expected = ACCOUNT_HASH_LENGTH; + MappingError::InvalidAccountHashLength { expected, actual } + } + + pub fn invalid_deploy_hash_length(actual: usize) -> Self { + let expected = KEY_HASH_LENGTH; + MappingError::InvalidDeployHashLength { expected, actual } + } + + pub fn invalid_hash_length(actual: usize) -> Self { + let expected = DEPLOY_HASH_LENGTH; + MappingError::InvalidHashLength { expected, actual } + } +} + +impl From for MappingError { + fn from(error: ParsingError) -> Self { + MappingError::Parsing(error) + } +} + +// This is whackadoodle, we know +impl From for engine_state::Error { + fn from(error: MappingError) -> Self { + match error { + MappingError::InvalidStateHashLength { expected, actual } => { + engine_state::Error::InvalidHashLength { expected, actual } + } + _ => engine_state::Error::Deploy, + } + } +} + +impl Display for MappingError { + fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + match self { + MappingError::InvalidStateHashLength { expected, actual } => write!( + f, + "Invalid hash length: expected {}, actual {}", + expected, actual + ), + MappingError::InvalidAccountHashLength { expected, actual } => write!( + f, + "Invalid public key length: expected {}, actual {}", + expected, actual + ), + MappingError::InvalidDeployHashLength { expected, actual } => write!( + f, + "Invalid deploy hash length: expected {}, actual {}", + expected, actual + ), + MappingError::Parsing(ParsingError(message)) => write!(f, "Parsing error: {}", message), + MappingError::InvalidStateHash(message) => write!(f, "Invalid hash: {}", message), + MappingError::MissingPayload => write!(f, "Missing payload"), + MappingError::TryFromSlice => write!(f, "Unable to convert from slice"), + MappingError::InvalidHashLength { expected, actual } => write!( + f, + "Invalid hash length: expected {}, actual {}", + expected, actual + ), + } + } +} + +#[derive(Debug, Eq, PartialEq)] +pub struct ParsingError(pub String); + +impl From for ParsingError { + fn from(error: T) -> Self { + ParsingError(error.to_string()) + } +} + +#[cfg(test)] +pub mod test_utils { + use std::{any, convert::TryFrom, fmt::Debug}; + + /// Checks that domain object `original` can be converted into a corresponding protobuf object + /// and back, and that the conversions yield an equal object to `original`. + pub fn protobuf_round_trip(original: T) + where + T: Clone + PartialEq + Debug + TryFrom, + >::Error: Debug, + U: From, + { + let pb_object = U::from(original.clone()); + let parsed = T::try_from(pb_object).unwrap_or_else(|_| { + panic!( + "Expected transforming {} into {} to succeed.", + any::type_name::(), + any::type_name::() + ) + }); + assert_eq!(original, parsed); + } +} + +#[cfg(test)] +mod tests { + use super::vec_to_array; + + #[test] + fn vec_to_array_test() { + assert_eq!([1; 32], vec_to_array(vec![1; 32], "").unwrap()); + assert!(vec_to_array(vec![], "").is_err()); + assert!(vec_to_array(vec![1; 31], "").is_err()); + assert!(vec_to_array(vec![1; 33], "").is_err()); + } +} diff --git a/grpc/server/src/engine_server/mappings/state/account.rs b/grpc/server/src/engine_server/mappings/state/account.rs new file mode 100644 index 0000000000..896f035578 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/account.rs @@ -0,0 +1,154 @@ +use std::{ + collections::BTreeMap, + convert::{TryFrom, TryInto}, + mem, +}; + +use node::contract_shared::account::{Account, ActionThresholds, AssociatedKeys}; +use types::account::{AccountHash, Weight}; + +use super::NamedKeyMap; +use crate::engine_server::{ + mappings::{self, ParsingError}, + state::{self, Account_AssociatedKey, NamedKey}, +}; + +impl From for state::Account { + fn from(mut account: Account) -> Self { + let mut pb_account = state::Account::new(); + + pb_account.public_key = account.account_hash().as_bytes().to_vec(); + + let named_keys = mem::replace(account.named_keys_mut(), BTreeMap::new()); + let pb_named_keys: Vec = NamedKeyMap::new(named_keys).into(); + pb_account.set_named_keys(pb_named_keys.into()); + + pb_account.set_main_purse(account.main_purse().into()); + + let associated_keys: Vec = + account.get_associated_keys().map(Into::into).collect(); + pb_account.set_associated_keys(associated_keys.into()); + + { + let deployment = u32::from(account.action_thresholds().deployment().value()); + let key_management = u32::from(account.action_thresholds().key_management().value()); + let pb_action_thresholds = pb_account.mut_action_thresholds(); + pb_action_thresholds.set_deployment_threshold(deployment); + pb_action_thresholds.set_key_management_threshold(key_management) + } + + pb_account + } +} + +impl TryFrom for Account { + type Error = ParsingError; + + fn try_from(pb_account: state::Account) -> Result { + let account_hash = + mappings::vec_to_array(pb_account.public_key, "Protobuf Account::AccountHash")?; + + let named_keys: NamedKeyMap = pb_account.named_keys.into_vec().try_into()?; + + let main_purse = { + let pb_uref = pb_account + .main_purse + .into_option() + .ok_or_else(|| ParsingError::from("Protobuf Account missing MainPurse field"))?; + pb_uref.try_into()? + }; + + let associated_keys = { + let mut associated_keys = AssociatedKeys::default(); + for pb_associated_key in pb_account.associated_keys.into_vec() { + let (key, weight) = pb_associated_key.try_into()?; + associated_keys.add_key(key, weight).map_err(|error| { + ParsingError(format!( + "Error parsing Protobuf Account::AssociatedKeys: {:?}", + error + )) + })?; + } + associated_keys + }; + + let action_thresholds = { + let pb_action_thresholds = + pb_account.action_thresholds.into_option().ok_or_else(|| { + ParsingError::from("Protobuf Account missing ActionThresholds field") + })?; + + ActionThresholds::new( + weight_from( + pb_action_thresholds.deployment_threshold, + "Protobuf DeploymentThreshold", + )?, + weight_from( + pb_action_thresholds.key_management_threshold, + "Protobuf KeyManagementThreshold", + )?, + ) + .map_err(ParsingError::from)? + }; + + let account = Account::new( + AccountHash::new(account_hash), + named_keys.into_inner(), + main_purse, + associated_keys, + action_thresholds, + ); + Ok(account) + } +} + +impl From<(&AccountHash, &Weight)> for Account_AssociatedKey { + fn from((account_hash, weight): (&AccountHash, &Weight)) -> Self { + let mut pb_associated_key = Account_AssociatedKey::new(); + pb_associated_key.public_key = account_hash.as_bytes().to_vec(); + pb_associated_key.set_weight(weight.value().into()); + pb_associated_key + } +} + +impl TryFrom for (AccountHash, Weight) { + type Error = ParsingError; + + fn try_from(pb_associated_key: Account_AssociatedKey) -> Result { + let account_hash = AccountHash::new(mappings::vec_to_array( + pb_associated_key.public_key, + "Protobuf Account::AssociatedKey", + )?); + + let weight = weight_from(pb_associated_key.weight, "Protobuf AssociatedKey::Weight")?; + + Ok((account_hash, weight)) + } +} + +fn weight_from(value: u32, value_name: &str) -> Result { + let weight = u8::try_from(value).map_err(|_| { + ParsingError(format!( + "Unable to convert {} to u8 while parsing {}", + value, value_name + )) + })?; + Ok(Weight::new(weight)) +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use node::contract_shared::account::gens; + + use super::*; + use crate::engine_server::mappings::test_utils; + + proptest! { + #[test] + fn round_trip(account in gens::account_arb()) { + test_utils::protobuf_round_trip::(account); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/state/big_int.rs b/grpc/server/src/engine_server/mappings/state/big_int.rs new file mode 100644 index 0000000000..77ddbe9b5a --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/big_int.rs @@ -0,0 +1,136 @@ +use std::convert::TryFrom; + +use types::{CLValue, U128, U256, U512}; + +use crate::engine_server::{mappings::ParsingError, state::BigInt}; + +impl TryFrom for CLValue { + type Error = ParsingError; + + fn try_from(pb_big_int: BigInt) -> Result { + let cl_value_result = match pb_big_int.get_bit_width() { + 128 => CLValue::from_t(U128::try_from(pb_big_int)?), + 256 => CLValue::from_t(U256::try_from(pb_big_int)?), + 512 => CLValue::from_t(U512::try_from(pb_big_int)?), + other => return Err(invalid_bit_width(other)), + }; + cl_value_result.map_err(|error| ParsingError(format!("{:?}", error))) + } +} + +fn invalid_bit_width(bit_width: u32) -> ParsingError { + ParsingError(format!( + "Protobuf BigInt bit width of {} is invalid", + bit_width + )) +} + +macro_rules! protobuf_conversions_for_uint { + ($type:ty, $bit_width:literal) => { + impl From<$type> for BigInt { + fn from(value: $type) -> Self { + let mut pb_big_int = BigInt::new(); + pb_big_int.set_value(format!("{}", value)); + pb_big_int.set_bit_width($bit_width); + pb_big_int + } + } + + impl TryFrom for $type { + type Error = ParsingError; + fn try_from(pb_big_int: BigInt) -> Result { + let value = pb_big_int.get_value(); + match pb_big_int.get_bit_width() { + $bit_width => <$type>::from_dec_str(value) + .map_err(|error| ParsingError(format!("{:?}", error))), + other => Err(invalid_bit_width(other)), + } + } + } + }; +} + +protobuf_conversions_for_uint!(U128, 128); +protobuf_conversions_for_uint!(U256, 256); +protobuf_conversions_for_uint!(U512, 512); + +#[cfg(test)] +mod tests { + use std::fmt::Debug; + + use proptest::proptest; + + use types::gens; + + use super::*; + use crate::engine_server::mappings::test_utils; + + proptest! { + #[test] + fn u128_round_trip(u128 in gens::u128_arb()) { + test_utils::protobuf_round_trip::(u128); + } + + #[test] + fn u256_round_trip(u256 in gens::u256_arb()) { + test_utils::protobuf_round_trip::(u256); + } + + #[test] + fn u512_round_trip(u512 in gens::u512_arb()) { + test_utils::protobuf_round_trip::(u512); + } + } + + fn try_with_bad_value(value: T) + where + T: Debug + Into + TryFrom, + >::Error: Debug + Into, + { + let expected_error = ParsingError("InvalidCharacter".to_string()); + + let mut invalid_pb_big_int = value.into(); + invalid_pb_big_int.set_value("a".to_string()); + + assert_eq!( + expected_error, + T::try_from(invalid_pb_big_int.clone()).unwrap_err().into() + ); + assert_eq!( + expected_error, + CLValue::try_from(invalid_pb_big_int).unwrap_err() + ); + } + + fn try_with_invalid_bit_width(value: T) + where + T: Debug + Into + TryFrom, + >::Error: Debug + Into, + { + let bit_width = 127; + let expected_error = invalid_bit_width(bit_width); + + let mut invalid_pb_big_int = value.into(); + invalid_pb_big_int.set_bit_width(bit_width); + + assert_eq!( + expected_error, + T::try_from(invalid_pb_big_int.clone()).unwrap_err().into() + ); + assert_eq!( + expected_error, + CLValue::try_from(invalid_pb_big_int).unwrap_err() + ); + } + + #[test] + fn should_fail_to_parse() { + try_with_bad_value(U128::one()); + try_with_bad_value(U256::one()); + try_with_bad_value(U512::one()); + + try_with_invalid_bit_width(U128::one()); + try_with_invalid_bit_width(U256::one()); + try_with_invalid_bit_width(U512::one()); + } +} diff --git a/grpc/server/src/engine_server/mappings/state/cl_type.rs b/grpc/server/src/engine_server/mappings/state/cl_type.rs new file mode 100644 index 0000000000..3cfc46a989 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/cl_type.rs @@ -0,0 +1,144 @@ +use std::convert::{TryFrom, TryInto}; + +use types::CLType; + +use crate::engine_server::{ + mappings::ParsingError, + state::{self, CLType_Simple, CLType_oneof_variants}, +}; + +impl From for state::CLType { + fn from(cl_type: CLType) -> Self { + let mut pb_type = state::CLType::new(); + match cl_type { + CLType::Bool => pb_type.set_simple_type(state::CLType_Simple::BOOL), + CLType::I32 => pb_type.set_simple_type(state::CLType_Simple::I32), + CLType::I64 => pb_type.set_simple_type(state::CLType_Simple::I64), + CLType::U8 => pb_type.set_simple_type(state::CLType_Simple::U8), + CLType::U32 => pb_type.set_simple_type(state::CLType_Simple::U32), + CLType::U64 => pb_type.set_simple_type(state::CLType_Simple::U64), + CLType::U128 => pb_type.set_simple_type(state::CLType_Simple::U128), + CLType::U256 => pb_type.set_simple_type(state::CLType_Simple::U256), + CLType::U512 => pb_type.set_simple_type(state::CLType_Simple::U512), + CLType::Unit => pb_type.set_simple_type(state::CLType_Simple::UNIT), + CLType::String => pb_type.set_simple_type(state::CLType_Simple::STRING), + CLType::Key => pb_type.set_simple_type(state::CLType_Simple::KEY), + CLType::URef => pb_type.set_simple_type(state::CLType_Simple::UREF), + CLType::Option(inner) => { + pb_type.mut_option_type().set_inner((*inner).into()); + } + CLType::List(inner) => { + pb_type.mut_list_type().set_inner((*inner).into()); + } + CLType::FixedList(inner, len) => { + let pb_fixed_list = pb_type.mut_fixed_list_type(); + pb_fixed_list.set_inner((*inner).into()); + pb_fixed_list.set_len(len); + } + CLType::Result { ok, err } => { + let pb_result = pb_type.mut_result_type(); + pb_result.set_ok((*ok).into()); + pb_result.set_err((*err).into()); + } + CLType::Map { key, value } => { + let pb_map = pb_type.mut_map_type(); + pb_map.set_key((*key).into()); + pb_map.set_value((*value).into()); + } + #[allow(clippy::redundant_clone)] + CLType::Tuple1(types) => { + pb_type + .mut_tuple1_type() + .set_type0((*types[0].clone()).into()); + } + #[allow(clippy::redundant_clone)] + CLType::Tuple2(types) => { + let pb_tuple2 = pb_type.mut_tuple2_type(); + pb_tuple2.set_type0((*types[0].clone()).into()); + pb_tuple2.set_type1((*types[1].clone()).into()); + } + #[allow(clippy::redundant_clone)] + CLType::Tuple3(types) => { + let pb_tuple3 = pb_type.mut_tuple3_type(); + pb_tuple3.set_type0((*types[0].clone()).into()); + pb_tuple3.set_type1((*types[1].clone()).into()); + pb_tuple3.set_type2((*types[2].clone()).into()); + } + CLType::Any => { + let _pb_any = pb_type.mut_any_type(); + } + }; + pb_type + } +} + +impl TryFrom for CLType { + type Error = ParsingError; + + fn try_from(pb_type: state::CLType) -> Result { + let pb_type = pb_type + .variants + .ok_or_else(|| ParsingError("Unable to parse Protobuf CLType".to_string()))?; + + let cl_type = match pb_type { + CLType_oneof_variants::simple_type(CLType_Simple::BOOL) => CLType::Bool, + CLType_oneof_variants::simple_type(CLType_Simple::I32) => CLType::I32, + CLType_oneof_variants::simple_type(CLType_Simple::I64) => CLType::I64, + CLType_oneof_variants::simple_type(CLType_Simple::U8) => CLType::U8, + CLType_oneof_variants::simple_type(CLType_Simple::U32) => CLType::U32, + CLType_oneof_variants::simple_type(CLType_Simple::U64) => CLType::U64, + CLType_oneof_variants::simple_type(CLType_Simple::U128) => CLType::U128, + CLType_oneof_variants::simple_type(CLType_Simple::U256) => CLType::U256, + CLType_oneof_variants::simple_type(CLType_Simple::U512) => CLType::U512, + CLType_oneof_variants::simple_type(CLType_Simple::UNIT) => CLType::Unit, + CLType_oneof_variants::simple_type(CLType_Simple::STRING) => CLType::String, + CLType_oneof_variants::simple_type(CLType_Simple::KEY) => CLType::Key, + CLType_oneof_variants::simple_type(CLType_Simple::UREF) => CLType::URef, + CLType_oneof_variants::option_type(mut pb_option) => { + let inner = pb_option.take_inner().try_into()?; + CLType::Option(Box::new(inner)) + } + CLType_oneof_variants::list_type(mut pb_list) => { + let inner = pb_list.take_inner().try_into()?; + CLType::List(Box::new(inner)) + } + CLType_oneof_variants::fixed_list_type(mut pb_fixed_list) => { + let inner = pb_fixed_list.take_inner().try_into()?; + CLType::FixedList(Box::new(inner), pb_fixed_list.len) + } + CLType_oneof_variants::result_type(mut pb_result) => { + let ok = pb_result.take_ok().try_into()?; + let err = pb_result.take_err().try_into()?; + CLType::Result { + ok: Box::new(ok), + err: Box::new(err), + } + } + CLType_oneof_variants::map_type(mut pb_map) => { + let key = pb_map.take_key().try_into()?; + let value = pb_map.take_value().try_into()?; + CLType::Map { + key: Box::new(key), + value: Box::new(value), + } + } + CLType_oneof_variants::tuple1_type(mut pb_tuple1) => { + let type0 = pb_tuple1.take_type0().try_into()?; + CLType::Tuple1([Box::new(type0)]) + } + CLType_oneof_variants::tuple2_type(mut pb_tuple2) => { + let type0 = pb_tuple2.take_type0().try_into()?; + let type1 = pb_tuple2.take_type1().try_into()?; + CLType::Tuple2([Box::new(type0), Box::new(type1)]) + } + CLType_oneof_variants::tuple3_type(mut pb_tuple3) => { + let type0 = pb_tuple3.take_type0().try_into()?; + let type1 = pb_tuple3.take_type1().try_into()?; + let type2 = pb_tuple3.take_type2().try_into()?; + CLType::Tuple3([Box::new(type0), Box::new(type1), Box::new(type2)]) + } + CLType_oneof_variants::any_type(_) => CLType::Any, + }; + Ok(cl_type) + } +} diff --git a/grpc/server/src/engine_server/mappings/state/cl_value.rs b/grpc/server/src/engine_server/mappings/state/cl_value.rs new file mode 100644 index 0000000000..0e54f164cf --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/cl_value.rs @@ -0,0 +1,43 @@ +use std::convert::{TryFrom, TryInto}; + +use types::CLValue; + +use crate::engine_server::{mappings::ParsingError, state}; + +impl From for state::CLValue { + fn from(cl_value: CLValue) -> Self { + let (cl_type, bytes) = cl_value.destructure(); + + let mut pb_value = state::CLValue::new(); + pb_value.set_cl_type(cl_type.into()); + pb_value.set_serialized_value(bytes); + + pb_value + } +} + +impl TryFrom for CLValue { + type Error = ParsingError; + + fn try_from(mut pb_value: state::CLValue) -> Result { + let cl_type = pb_value.take_cl_type().try_into()?; + Ok(CLValue::from_components(cl_type, pb_value.serialized_value)) + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use types::gens; + + use super::*; + use crate::engine_server::mappings::test_utils; + + proptest! { + #[test] + fn round_trip(cl_value in gens::cl_value_arb()) { + test_utils::protobuf_round_trip::(cl_value); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/state/contract.rs b/grpc/server/src/engine_server/mappings/state/contract.rs new file mode 100644 index 0000000000..7d7a400deb --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/contract.rs @@ -0,0 +1,83 @@ +use types::{ + contracts::{Contract, NamedKeys}, + ContractPackageHash, ContractWasmHash, EntryPoints, +}; + +use super::NamedKeyMap; +use crate::engine_server::{mappings::ParsingError, state}; +use std::convert::{TryFrom, TryInto}; + +impl From for state::Contract { + fn from(contract: Contract) -> Self { + let (contract_package_hash, contract_wasm_hash, named_keys, entry_points, protocol_version) = + contract.into(); + let mut pb_contract = state::Contract::new(); + let named_keys: Vec = NamedKeyMap::new(named_keys).into(); + let entry_points: Vec = entry_points + .take_entry_points() + .into_iter() + .map(Into::into) + .collect(); + pb_contract.set_contract_package_hash(contract_package_hash.to_vec()); + pb_contract.set_contract_wasm_hash(contract_wasm_hash.to_vec()); + pb_contract.set_named_keys(named_keys.into()); + pb_contract.set_entry_points(entry_points.into()); + pb_contract.set_protocol_version(protocol_version.into()); + pb_contract + } +} + +impl TryFrom for Contract { + type Error = ParsingError; + fn try_from(mut value: state::Contract) -> Result { + let named_keys = { + let mut named_keys = NamedKeys::new(); + for mut named_key in value.take_named_keys().into_iter() { + named_keys.insert(named_key.take_name(), named_key.take_key().try_into()?); + } + named_keys + }; + + let contract_package_hash: ContractPackageHash = value + .contract_package_hash + .as_slice() + .try_into() + .map_err(|_| ParsingError::from("Unable to parse contract package hash"))?; + let contract_wasm_hash: ContractWasmHash = + value + .contract_wasm_hash + .as_slice() + .try_into() + .map_err(|_| ParsingError::from("Unable to parse contract package hash"))?; + + let mut entry_points = EntryPoints::new(); + for entry_point in value.take_entry_points().into_iter() { + entry_points.add_entry_point(entry_point.try_into()?); + } + + Ok(Contract::new( + contract_package_hash, + contract_wasm_hash, + named_keys, + entry_points, + value.take_protocol_version().try_into()?, + )) + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use super::*; + use crate::engine_server::mappings::test_utils; + use types::gens; + + proptest! { + + #[test] + fn round_trip(contract in gens::contract_arb()) { + test_utils::protobuf_round_trip::(contract); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/state/contract_package.rs b/grpc/server/src/engine_server/mappings/state/contract_package.rs new file mode 100644 index 0000000000..104e212eaf --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/contract_package.rs @@ -0,0 +1,208 @@ +use std::{ + collections::BTreeSet, + convert::{TryFrom, TryInto}, +}; +use types::{ + contracts::{ContractVersions, DisabledVersions, Groups}, + ContractPackage, ContractVersionKey, EntryPoint, EntryPointAccess, EntryPointType, Group, + Parameter, +}; + +use crate::engine_server::{mappings::ParsingError, state}; + +impl From for state::ContractPackage { + fn from(value: ContractPackage) -> state::ContractPackage { + let mut contract_package = state::ContractPackage::new(); + contract_package.set_access_key(value.access_key().into()); + + for &disabled_version in value.disabled_versions().iter() { + contract_package + .mut_disabled_versions() + .push(disabled_version.into()) + } + + for (existing_group, urefs) in value.groups().iter() { + let mut entrypoint_group = state::Contract_EntryPoint_Group::new(); + entrypoint_group.set_name(existing_group.value().to_string()); + + let mut contract_package_group = state::ContractPackage_Group::new(); + contract_package_group.set_group(entrypoint_group); + + for &uref in urefs { + contract_package_group.mut_urefs().push(uref.into()); + } + + contract_package.mut_groups().push(contract_package_group); + } + + for (version, contract_header) in value.take_versions().into_iter() { + let mut active_version = state::ContractPackage_Version::new(); + active_version.set_version(version.into()); + active_version.set_contract_hash(contract_header.to_vec()); + contract_package.mut_active_versions().push(active_version) + } + + contract_package + } +} + +impl TryFrom for ContractPackage { + type Error = ParsingError; + fn try_from(mut value: state::ContractPackage) -> Result { + let access_uref = value.take_access_key().try_into()?; + let mut contract_package = ContractPackage::new( + access_uref, + ContractVersions::default(), + DisabledVersions::default(), + Groups::default(), + ); + for mut active_version in value.take_active_versions().into_iter() { + let version = active_version.take_version().try_into()?; + let header = active_version.take_contract_hash().as_slice().try_into()?; + contract_package.versions_mut().insert(version, header); + } + for disabled_version in value.take_disabled_versions().into_iter() { + contract_package + .disabled_versions_mut() + .insert(disabled_version.try_into()?); + } + + let groups = contract_package.groups_mut(); + for mut group in value.take_groups().into_iter() { + let group_name = group.take_group().take_name(); + let mut urefs = BTreeSet::new(); + for uref in group.take_urefs().into_iter() { + urefs.insert(uref.try_into()?); + } + groups.insert(Group::new(group_name), urefs); + } + Ok(contract_package) + } +} + +impl From for state::Contract_EntryPoint { + fn from(value: EntryPoint) -> Self { + let (name, args, ret, entry_point_access, entry_point_type) = value.into(); + + let mut res = state::Contract_EntryPoint::new(); + res.set_name(name); + + for arg in args.into_iter() { + let (name, cl_type) = arg.into(); + let mut state_arg = state::Contract_EntryPoint_Arg::new(); + + state_arg.set_name(name); + state_arg.set_cl_type(cl_type.into()); + + res.mut_args().push(state_arg) + } + + res.set_ret(ret.into()); + + match entry_point_access { + EntryPointAccess::Public => res.set_public(state::Contract_EntryPoint_Public::new()), + EntryPointAccess::Groups(groups) => { + let mut state_groups = state::Contract_EntryPoint_Groups::new(); + for group in groups.into_iter() { + let mut state_group = state::Contract_EntryPoint_Group::new(); + let name = group.into(); + state_group.set_name(name); + state_groups.mut_groups().push(state_group); + } + res.set_groups(state_groups) + } + } + + match entry_point_type { + EntryPointType::Session => { + res.set_session(state::Contract_EntryPoint_SessionType::new()) + } + EntryPointType::Contract => { + res.set_contract(state::Contract_EntryPoint_ContractType::new()) + } + } + res + } +} + +impl TryFrom for EntryPoint { + type Error = ParsingError; + fn try_from(mut value: state::Contract_EntryPoint) -> Result { + let name = value.take_name(); + let mut args = Vec::new(); + + let ret = value.take_ret().try_into()?; + + for mut arg in value.take_args().into_iter() { + args.push(Parameter::new( + arg.take_name(), + arg.take_cl_type().try_into()?, + )); + } + + let entry_point_access = match value.access { + Some(state::Contract_EntryPoint_oneof_access::public(_)) => EntryPointAccess::Public, + Some(state::Contract_EntryPoint_oneof_access::groups(mut groups)) => { + let mut vec = Vec::new(); + for mut group in groups.take_groups().into_iter() { + vec.push(Group::new(group.take_name())); + } + EntryPointAccess::Groups(vec) + } + None => return Err("Unable to parse Protobuf entry point access".into()), + }; + let entry_point_type = match value.entry_point_type { + Some(state::Contract_EntryPoint_oneof_entry_point_type::session(_)) => { + EntryPointType::Session + } + Some(state::Contract_EntryPoint_oneof_entry_point_type::contract(_)) => { + EntryPointType::Contract + } + None => return Err("Unable to parse Protobuf entry point type".into()), + }; + Ok(EntryPoint::new( + name, + args, + ret, + entry_point_access, + entry_point_type, + )) + } +} + +impl From for state::ContractVersionKey { + fn from(version: ContractVersionKey) -> Self { + let mut contract_version_key = state::ContractVersionKey::new(); + contract_version_key.set_protocol_version_major(version.protocol_version_major()); + contract_version_key.set_contract_version(version.contract_version()); + contract_version_key + } +} + +impl TryFrom for ContractVersionKey { + type Error = ParsingError; + fn try_from(value: state::ContractVersionKey) -> Result { + let contract_version = value.contract_version; + Ok(ContractVersionKey::new( + value.protocol_version_major, + contract_version, + )) + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use super::*; + use crate::engine_server::mappings::test_utils; + use types::gens; + + proptest! { + + #[test] + fn round_trip(contract in gens::contract_package_arb()) { + test_utils::protobuf_round_trip::(contract); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/state/contract_wasm.rs b/grpc/server/src/engine_server/mappings/state/contract_wasm.rs new file mode 100644 index 0000000000..1def893d1c --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/contract_wasm.rs @@ -0,0 +1,32 @@ +use crate::engine_server::state; +use types::ContractWasm; + +impl From for state::ContractWasm { + fn from(contract: ContractWasm) -> Self { + let mut pb_contract_wasm = state::ContractWasm::new(); + pb_contract_wasm.set_wasm(contract.take_bytes()); + pb_contract_wasm + } +} + +impl From for ContractWasm { + fn from(mut contract: state::ContractWasm) -> Self { + ContractWasm::new(contract.take_wasm()) + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use super::*; + use crate::engine_server::mappings::test_utils; + use types::gens; + + proptest! { + #[test] + fn round_trip(contract_wasm in gens::contract_wasm_arb()) { + test_utils::protobuf_round_trip::(contract_wasm); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/state/key.rs b/grpc/server/src/engine_server/mappings/state/key.rs new file mode 100644 index 0000000000..971ff14329 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/key.rs @@ -0,0 +1,73 @@ +use std::convert::{TryFrom, TryInto}; + +use types::{account::AccountHash, Key}; + +use crate::engine_server::{ + mappings::{self, ParsingError}, + state::{self, Key_Address, Key_Hash, Key_oneof_value}, +}; + +impl From for state::Key { + fn from(key: Key) -> Self { + let mut pb_key = state::Key::new(); + match key { + Key::Account(account) => { + let mut pb_account = Key_Address::new(); + pb_account.set_account(account.as_bytes().to_vec()); + pb_key.set_address(pb_account); + } + Key::Hash(hash) => { + let mut pb_hash = Key_Hash::new(); + pb_hash.set_hash(hash.to_vec()); + pb_key.set_hash(pb_hash); + } + Key::URef(uref) => { + pb_key.set_uref(uref.into()); + } + } + pb_key + } +} + +impl TryFrom for Key { + type Error = ParsingError; + + fn try_from(pb_key: state::Key) -> Result { + let pb_key = pb_key + .value + .ok_or_else(|| ParsingError::from("Unable to parse Protobuf Key"))?; + + let key = match pb_key { + Key_oneof_value::address(pb_account) => { + let account = mappings::vec_to_array(pb_account.account, "Protobuf Key::Account")?; + Key::Account(AccountHash::new(account)) + } + Key_oneof_value::hash(pb_hash) => { + let hash = mappings::vec_to_array(pb_hash.hash, "Protobuf Key::Hash")?; + Key::Hash(hash) + } + Key_oneof_value::uref(pb_uref) => { + let uref = pb_uref.try_into()?; + Key::URef(uref) + } + }; + Ok(key) + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use types::gens; + + use super::*; + use crate::engine_server::mappings::test_utils; + + proptest! { + #[test] + fn round_trip(key in gens::key_arb()) { + test_utils::protobuf_round_trip::(key); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/state/mod.rs b/grpc/server/src/engine_server/mappings/state/mod.rs new file mode 100644 index 0000000000..3907b1e7a0 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/mod.rs @@ -0,0 +1,18 @@ +//! Functions for converting between CasperLabs types and their Protobuf equivalents which are +//! defined in protobuf/io/casperlabs/casper/consensus/state.proto + +mod account; +mod big_int; +mod cl_type; +mod cl_value; +mod contract; +mod contract_package; +mod contract_wasm; +mod key; +mod named_key; +mod protocol_version; +mod semver; +mod stored_value; +mod uref; + +pub(crate) use named_key::NamedKeyMap; diff --git a/grpc/server/src/engine_server/mappings/state/named_key.rs b/grpc/server/src/engine_server/mappings/state/named_key.rs new file mode 100644 index 0000000000..b2a5972d29 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/named_key.rs @@ -0,0 +1,85 @@ +use std::{ + collections::BTreeMap, + convert::{TryFrom, TryInto}, +}; + +use types::{contracts::NamedKeys, Key}; + +use crate::engine_server::{mappings::ParsingError, state::NamedKey}; + +impl From<(String, Key)> for NamedKey { + fn from((name, key): (String, Key)) -> Self { + let mut pb_named_key = NamedKey::new(); + pb_named_key.set_name(name); + pb_named_key.set_key(key.into()); + pb_named_key + } +} + +impl TryFrom for (String, Key) { + type Error = ParsingError; + + fn try_from(mut pb_named_key: NamedKey) -> Result { + let key = pb_named_key.take_key().try_into()?; + let name = pb_named_key.name; + Ok((name, key)) + } +} + +/// Thin wrapper to allow us to implement `From` and `TryFrom` helpers to convert to and from +/// `NamedKeys` and `Vec`. +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct NamedKeyMap(NamedKeys); + +impl NamedKeyMap { + pub fn new(inner: NamedKeys) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> NamedKeys { + self.0 + } +} + +impl From for Vec { + fn from(named_key_map: NamedKeyMap) -> Self { + named_key_map.0.into_iter().map(Into::into).collect() + } +} + +impl TryFrom> for NamedKeyMap { + type Error = ParsingError; + + fn try_from(pb_named_keys: Vec) -> Result { + let mut named_key_map = NamedKeyMap(BTreeMap::new()); + for pb_named_key in pb_named_keys { + let (name, key) = pb_named_key.try_into()?; + // TODO - consider returning an error if `insert()` returns `Some`. + let _ = named_key_map.0.insert(name, key); + } + Ok(named_key_map) + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use types::gens; + + use super::*; + use crate::engine_server::mappings::test_utils; + + proptest! { + #[test] + fn round_trip(string in "\\PC*", key in gens::key_arb()) { + test_utils::protobuf_round_trip::<(String, Key), NamedKey>((string, key)); + } + + #[test] + fn map_round_trip(named_keys in gens::named_keys_arb(10)) { + let named_key_map = NamedKeyMap(named_keys); + test_utils::protobuf_round_trip::>(named_key_map); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/state/protocol_version.rs b/grpc/server/src/engine_server/mappings/state/protocol_version.rs new file mode 100644 index 0000000000..5e16bf4ed6 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/protocol_version.rs @@ -0,0 +1,43 @@ +use types::ProtocolVersion; + +use crate::engine_server::state; + +impl From for state::ProtocolVersion { + fn from(protocol_version: ProtocolVersion) -> Self { + let sem_ver = protocol_version.value(); + state::ProtocolVersion { + major: sem_ver.major, + minor: sem_ver.minor, + patch: sem_ver.patch, + ..Default::default() + } + } +} + +impl From for ProtocolVersion { + fn from(pb_protocol_version: state::ProtocolVersion) -> Self { + ProtocolVersion::from_parts( + pb_protocol_version.major, + pb_protocol_version.minor, + pb_protocol_version.patch, + ) + } +} + +#[cfg(test)] +mod tests { + use proptest::{prelude::any, proptest}; + + use super::*; + use crate::engine_server::mappings::test_utils; + + proptest! { + #[test] + fn round_trip((major, minor, patch) in any::<(u32, u32, u32)>()) { + let protocol_version = ProtocolVersion::from_parts(major, minor, patch); + test_utils::protobuf_round_trip::( + protocol_version, + ); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/state/semver.rs b/grpc/server/src/engine_server/mappings/state/semver.rs new file mode 100644 index 0000000000..e065e6d27a --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/semver.rs @@ -0,0 +1,19 @@ +use types::SemVer; + +use crate::engine_server::state; + +impl From for SemVer { + fn from(pb_semver: state::SemVer) -> Self { + Self::new(pb_semver.major, pb_semver.minor, pb_semver.patch) + } +} + +impl Into for SemVer { + fn into(self) -> state::SemVer { + let mut res = state::SemVer::new(); + res.set_major(self.major); + res.set_minor(self.minor); + res.set_patch(self.patch); + res + } +} diff --git a/grpc/server/src/engine_server/mappings/state/stored_value.rs b/grpc/server/src/engine_server/mappings/state/stored_value.rs new file mode 100644 index 0000000000..0a1d95d080 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/stored_value.rs @@ -0,0 +1,76 @@ +use node::contract_shared::stored_value::StoredValue; + +use crate::engine_server::{ + mappings::ParsingError, + state::{self, StoredValue_oneof_variants}, +}; +use std::convert::{TryFrom, TryInto}; + +impl From for state::StoredValue { + fn from(value: StoredValue) -> Self { + let mut pb_value = state::StoredValue::new(); + + match value { + StoredValue::CLValue(cl_value) => pb_value.set_cl_value(cl_value.into()), + StoredValue::Account(account) => pb_value.set_account(account.into()), + StoredValue::Contract(contract) => { + pb_value.set_contract(contract.into()); + } + StoredValue::ContractWasm(contract_wasm) => { + pb_value.set_contract_wasm(contract_wasm.into()) + } + StoredValue::ContractPackage(contract_package) => { + pb_value.set_contract_package(contract_package.into()) + } + } + + pb_value + } +} + +impl TryFrom for StoredValue { + type Error = ParsingError; + + fn try_from(pb_value: state::StoredValue) -> Result { + let pb_value = pb_value + .variants + .ok_or_else(|| ParsingError("Unable to parse Protobuf StoredValue".to_string()))?; + + let value = match pb_value { + StoredValue_oneof_variants::cl_value(pb_value) => { + StoredValue::CLValue(pb_value.try_into()?) + } + StoredValue_oneof_variants::account(pb_account) => { + StoredValue::Account(pb_account.try_into()?) + } + StoredValue_oneof_variants::contract(pb_contract) => { + StoredValue::Contract(pb_contract.try_into()?) + } + StoredValue_oneof_variants::contract_package(pb_contract_package) => { + StoredValue::ContractPackage(pb_contract_package.try_into()?) + } + StoredValue_oneof_variants::contract_wasm(pb_contract_wasm) => { + StoredValue::ContractWasm(pb_contract_wasm.into()) + } + }; + + Ok(value) + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use node::contract_shared::stored_value::gens; + + use super::*; + use crate::engine_server::mappings::test_utils; + + proptest! { + #[test] + fn round_trip(value in gens::stored_value_arb()) { + test_utils::protobuf_round_trip::(value); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/state/uref.rs b/grpc/server/src/engine_server/mappings/state/uref.rs new file mode 100644 index 0000000000..281dc8cbd0 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/state/uref.rs @@ -0,0 +1,104 @@ +use std::convert::TryFrom; + +use types::{AccessRights, URef}; + +use crate::engine_server::{ + mappings::{self, ParsingError}, + state::{Key_URef, Key_URef_AccessRights}, +}; + +impl From for Key_URef_AccessRights { + fn from(access_rights: AccessRights) -> Self { + match access_rights { + AccessRights::NONE => Key_URef_AccessRights::NONE, + AccessRights::READ => Key_URef_AccessRights::READ, + AccessRights::WRITE => Key_URef_AccessRights::WRITE, + AccessRights::ADD => Key_URef_AccessRights::ADD, + AccessRights::READ_ADD => Key_URef_AccessRights::READ_ADD, + AccessRights::READ_WRITE => Key_URef_AccessRights::READ_WRITE, + AccessRights::ADD_WRITE => Key_URef_AccessRights::ADD_WRITE, + AccessRights::READ_ADD_WRITE => Key_URef_AccessRights::READ_ADD_WRITE, + _ => Key_URef_AccessRights::NONE, + } + } +} + +impl From for Key_URef { + fn from(uref: URef) -> Self { + let mut pb_uref = Key_URef::new(); + pb_uref.set_uref(uref.addr().to_vec()); + let access_rights = uref.access_rights(); + pb_uref.set_access_rights(access_rights.into()); + pb_uref + } +} + +impl TryFrom for URef { + type Error = ParsingError; + + fn try_from(pb_uref: Key_URef) -> Result { + let addr = mappings::vec_to_array(pb_uref.uref, "Protobuf URef addr")?; + + let access_rights = match pb_uref.access_rights { + Key_URef_AccessRights::NONE => AccessRights::NONE, + Key_URef_AccessRights::READ => AccessRights::READ, + Key_URef_AccessRights::WRITE => AccessRights::WRITE, + Key_URef_AccessRights::ADD => AccessRights::ADD, + Key_URef_AccessRights::READ_ADD => AccessRights::READ_ADD, + Key_URef_AccessRights::READ_WRITE => AccessRights::READ_WRITE, + Key_URef_AccessRights::ADD_WRITE => AccessRights::ADD_WRITE, + Key_URef_AccessRights::READ_ADD_WRITE => AccessRights::READ_ADD_WRITE, + }; + + let uref = URef::new(addr, access_rights); + + Ok(uref) + } +} + +#[cfg(test)] +mod tests { + use types::UREF_ADDR_LENGTH; + + use super::*; + use crate::engine_server::mappings::test_utils; + + #[test] + fn round_trip() { + for access_rights in &[ + AccessRights::READ, + AccessRights::WRITE, + AccessRights::ADD, + AccessRights::READ_ADD, + AccessRights::READ_WRITE, + AccessRights::ADD_WRITE, + AccessRights::READ_ADD_WRITE, + ] { + let uref = URef::new(rand::random(), *access_rights); + test_utils::protobuf_round_trip::(uref); + } + + let uref = URef::new(rand::random(), AccessRights::READ).remove_access_rights(); + test_utils::protobuf_round_trip::(uref); + } + + #[test] + fn should_fail_to_parse() { + // Check we handle invalid Protobuf URefs correctly. + let empty_pb_uref = Key_URef::new(); + assert!(URef::try_from(empty_pb_uref).is_err()); + + let mut pb_uref_invalid_addr = Key_URef::new(); + pb_uref_invalid_addr.set_uref(vec![1; UREF_ADDR_LENGTH - 1]); + assert!(URef::try_from(pb_uref_invalid_addr).is_err()); + + // Check Protobuf URef with `AccessRights::UNKNOWN` parses to a URef with no access rights. + let addr: [u8; UREF_ADDR_LENGTH] = rand::random(); + let mut pb_uref = Key_URef::new(); + pb_uref.set_uref(addr.to_vec()); + pb_uref.set_access_rights(Key_URef_AccessRights::NONE); + let parsed_uref = URef::try_from(pb_uref).unwrap(); + assert_eq!(addr, parsed_uref.addr()); + assert!(parsed_uref.access_rights().is_none()); + } +} diff --git a/grpc/server/src/engine_server/mappings/transforms/error.rs b/grpc/server/src/engine_server/mappings/transforms/error.rs new file mode 100644 index 0000000000..6c24703433 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/transforms/error.rs @@ -0,0 +1,65 @@ +use std::convert::TryFrom; + +use node::contract_shared::{transform, TypeMismatch}; + +use crate::engine_server::{ + mappings::ParsingError, + transforms::{self, TransformFailure, TransformFailure_oneof_failure_instance}, +}; + +impl From for transforms::TypeMismatch { + fn from(type_mismatch: TypeMismatch) -> transforms::TypeMismatch { + let mut pb_type_mismatch = transforms::TypeMismatch::new(); + pb_type_mismatch.set_expected(type_mismatch.expected); + pb_type_mismatch.set_found(type_mismatch.found); + pb_type_mismatch + } +} + +impl From for TransformFailure { + fn from(error: transform::Error) -> Self { + let mut pb_transform_failure = TransformFailure::new(); + match error { + transform::Error::TypeMismatch(type_mismatch) => { + pb_transform_failure.set_type_mismatch(type_mismatch.into()) + } + transform::Error::Serialization(_error) => panic!("don't break the API"), + } + pb_transform_failure + } +} + +impl TryFrom for transform::Error { + type Error = ParsingError; + + fn try_from(pb_transform_failure: TransformFailure) -> Result { + let pb_transform_failure = pb_transform_failure + .failure_instance + .ok_or_else(|| ParsingError::from("Unable to parse Protobuf TransformFailure"))?; + match pb_transform_failure { + TransformFailure_oneof_failure_instance::type_mismatch(transforms::TypeMismatch { + expected, + found, + .. + }) => { + let type_mismatch = TypeMismatch { expected, found }; + Ok(transform::Error::TypeMismatch(type_mismatch)) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::engine_server::mappings::test_utils; + + #[test] + fn round_trip() { + let error = transform::Error::TypeMismatch(TypeMismatch::new( + "expected".to_string(), + "found".to_string(), + )); + test_utils::protobuf_round_trip::(error); + } +} diff --git a/grpc/server/src/engine_server/mappings/transforms/mod.rs b/grpc/server/src/engine_server/mappings/transforms/mod.rs new file mode 100644 index 0000000000..863480b62d --- /dev/null +++ b/grpc/server/src/engine_server/mappings/transforms/mod.rs @@ -0,0 +1,9 @@ +//! Functions for converting between CasperLabs types and their Protobuf equivalents which are +//! defined in protobuf/io/casperlabs/ipc/transforms.proto + +mod error; +mod transform; +mod transform_entry; +mod transform_map; + +pub use transform_map::TransformMap; diff --git a/grpc/server/src/engine_server/mappings/transforms/transform.rs b/grpc/server/src/engine_server/mappings/transforms/transform.rs new file mode 100644 index 0000000000..8770ab8a92 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/transforms/transform.rs @@ -0,0 +1,122 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_shared::{ + stored_value::StoredValue, + transform::{Error as TransformError, Transform}, +}; +use types::{CLType, CLValue, U128, U256, U512}; + +use crate::engine_server::{ + mappings::{state::NamedKeyMap, ParsingError}, + state::NamedKey, + transforms::{self, Transform_oneof_transform_instance}, +}; + +impl From for transforms::Transform { + fn from(transform: Transform) -> Self { + let mut pb_transform = transforms::Transform::new(); + match transform { + Transform::Identity => { + pb_transform.set_identity(Default::default()); + } + Transform::AddInt32(i) => { + pb_transform.mut_add_i32().set_value(i); + } + Transform::AddUInt64(u) => { + pb_transform.mut_add_u64().set_value(u); + } + Transform::Write(value) => { + pb_transform.mut_write().set_value(value.into()); + } + Transform::AddKeys(keys_map) => { + let pb_named_keys: Vec = NamedKeyMap::new(keys_map).into(); + pb_transform.mut_add_keys().set_value(pb_named_keys.into()); + } + Transform::Failure(transform_error) => pb_transform.set_failure(transform_error.into()), + Transform::AddUInt128(uint128) => { + pb_transform.mut_add_big_int().set_value(uint128.into()); + } + Transform::AddUInt256(uint256) => { + pb_transform.mut_add_big_int().set_value(uint256.into()); + } + Transform::AddUInt512(uint512) => { + pb_transform.mut_add_big_int().set_value(uint512.into()); + } + }; + pb_transform + } +} + +impl TryFrom for Transform { + type Error = ParsingError; + + fn try_from(pb_transform: transforms::Transform) -> Result { + let pb_transform = pb_transform + .transform_instance + .ok_or_else(|| ParsingError::from("Unable to parse Protobuf Transform"))?; + let transform = match pb_transform { + Transform_oneof_transform_instance::identity(_) => Transform::Identity, + Transform_oneof_transform_instance::add_keys(pb_add_keys) => { + let named_keys_map: NamedKeyMap = pb_add_keys.value.into_vec().try_into()?; + named_keys_map.into_inner().into() + } + Transform_oneof_transform_instance::add_i32(pb_add_int32) => pb_add_int32.value.into(), + Transform_oneof_transform_instance::add_u64(pb_add_u64) => pb_add_u64.value.into(), + Transform_oneof_transform_instance::add_big_int(mut pb_big_int) => { + let cl_value: CLValue = pb_big_int.take_value().try_into()?; + match cl_value.cl_type() { + CLType::U128 => { + let u128: U128 = cl_value + .into_t() + .map_err(|error| ParsingError(format!("{:?}", error)))?; + u128.into() + } + CLType::U256 => { + let u256: U256 = cl_value + .into_t() + .map_err(|error| ParsingError(format!("{:?}", error)))?; + u256.into() + } + CLType::U512 => { + let u512: U512 = cl_value + .into_t() + .map_err(|error| ParsingError(format!("{:?}", error)))?; + u512.into() + } + other => { + return Err(ParsingError(format!( + "Protobuf BigInt was turned into a non-uint Value type: {:?}", + other + ))); + } + } + } + Transform_oneof_transform_instance::write(mut pb_write) => { + let value = StoredValue::try_from(pb_write.take_value())?; + Transform::Write(value) + } + Transform_oneof_transform_instance::failure(pb_failure) => { + let error = TransformError::try_from(pb_failure)?; + Transform::Failure(error) + } + }; + Ok(transform) + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use node::contract_shared::transform::gens; + + use super::*; + use crate::engine_server::mappings::test_utils; + + proptest! { + #[test] + fn round_trip(transform in gens::transform_arb()) { + test_utils::protobuf_round_trip::(transform); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/transforms/transform_entry.rs b/grpc/server/src/engine_server/mappings/transforms/transform_entry.rs new file mode 100644 index 0000000000..693777ba38 --- /dev/null +++ b/grpc/server/src/engine_server/mappings/transforms/transform_entry.rs @@ -0,0 +1,55 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_shared::transform::Transform; +use types::Key; + +use crate::engine_server::{mappings::ParsingError, transforms::TransformEntry}; + +impl From<(Key, Transform)> for TransformEntry { + fn from((key, transform): (Key, Transform)) -> Self { + let mut pb_transform_entry = TransformEntry::new(); + pb_transform_entry.set_key(key.into()); + pb_transform_entry.set_transform(transform.into()); + pb_transform_entry + } +} + +impl TryFrom for (Key, Transform) { + type Error = ParsingError; + + fn try_from(pb_transform_entry: TransformEntry) -> Result { + let pb_key = pb_transform_entry + .key + .into_option() + .ok_or_else(|| ParsingError::from("Protobuf TransformEntry missing Key field"))?; + let key = pb_key.try_into()?; + + let pb_transform = pb_transform_entry + .transform + .into_option() + .ok_or_else(|| ParsingError::from("Protobuf TransformEntry missing Transform field"))?; + let transform = pb_transform.try_into()?; + + Ok((key, transform)) + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use node::contract_shared::transform; + + use super::*; + use crate::engine_server::mappings::test_utils; + + proptest! { + #[test] + fn round_trip( + key in types::gens::key_arb(), + transform in transform::gens::transform_arb() + ) { + test_utils::protobuf_round_trip::<(Key, Transform), TransformEntry>((key, transform)); + } + } +} diff --git a/grpc/server/src/engine_server/mappings/transforms/transform_map.rs b/grpc/server/src/engine_server/mappings/transforms/transform_map.rs new file mode 100644 index 0000000000..61bdcd455c --- /dev/null +++ b/grpc/server/src/engine_server/mappings/transforms/transform_map.rs @@ -0,0 +1,66 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_shared::{additive_map::AdditiveMap, transform::Transform}; +use types::Key; + +use crate::engine_server::{mappings::ParsingError, transforms::TransformEntry}; + +pub struct TransformMap(AdditiveMap); + +impl TransformMap { + pub fn into_inner(self) -> AdditiveMap { + self.0 + } +} + +impl TryFrom> for TransformMap { + type Error = ParsingError; + + fn try_from(pb_transform_map: Vec) -> Result { + let mut transforms_merged: AdditiveMap = AdditiveMap::new(); + for pb_transform_entry in pb_transform_map { + let (key, transform) = pb_transform_entry.try_into()?; + transforms_merged.insert_add(key, transform); + } + Ok(TransformMap(transforms_merged)) + } +} + +#[cfg(test)] +mod tests { + use node::contract_shared::stored_value::StoredValue; + use types::CLValue; + + use super::*; + + #[test] + fn commit_effects_merges_transforms() { + // Tests that transforms made to the same key are merged instead of lost. + let key = Key::Hash([1u8; 32]); + let setup: Vec = { + let transform_entry_first = { + let mut tmp = TransformEntry::new(); + tmp.set_key(key.into()); + tmp.set_transform( + Transform::Write(StoredValue::CLValue(CLValue::from_t(12_i32).unwrap())).into(), + ); + tmp + }; + let transform_entry_second = { + let mut tmp = TransformEntry::new(); + tmp.set_key(key.into()); + tmp.set_transform(Transform::AddInt32(10).into()); + tmp + }; + vec![transform_entry_first, transform_entry_second] + }; + let commit: TransformMap = setup + .try_into() + .expect("Transforming `Vec` into `TransformMap` should work."); + let expected_transform = + Transform::Write(StoredValue::CLValue(CLValue::from_t(22_i32).unwrap())); + let commit_transform = commit.0.get(&key); + assert!(commit_transform.is_some()); + assert_eq!(expected_transform, *commit_transform.unwrap()) + } +} diff --git a/grpc/server/src/engine_server/mod.rs b/grpc/server/src/engine_server/mod.rs new file mode 100644 index 0000000000..8a35b03b24 --- /dev/null +++ b/grpc/server/src/engine_server/mod.rs @@ -0,0 +1,512 @@ +include!(concat!( + env!("OUT_DIR"), + "/../../../../generated_protobuf/ipc.rs" +)); +include!(concat!( + env!("OUT_DIR"), + "/../../../../generated_protobuf/ipc_grpc.rs" +)); +include!(concat!( + env!("OUT_DIR"), + "/../../../../generated_protobuf/state.rs" +)); +include!(concat!( + env!("OUT_DIR"), + "/../../../../generated_protobuf/transforms.rs" +)); +pub mod mappings; + +use std::{ + collections::BTreeMap, + convert::{TryFrom, TryInto}, + fmt::Debug, + io::ErrorKind, + iter::FromIterator, + marker::{Send, Sync}, + time::Instant, +}; + +use grpc::{Error as GrpcError, RequestOptions, ServerBuilder, SingleResponse}; +use log::{info, warn, Level}; + +use node::contract_core::{ + engine_state::{ + execute_request::ExecuteRequest, + genesis::GenesisResult, + query::{QueryRequest, QueryResult}, + run_genesis_request::RunGenesisRequest, + upgrade::{UpgradeConfig, UpgradeResult}, + EngineState, Error as EngineError, + }, + execution, +}; +use node::contract_shared::{ + logging::{self, log_duration}, + newtypes::{Blake2bHash, CorrelationId}, +}; +use node::contract_storage::global_state::{CommitResult, StateProvider}; +use types::{bytesrepr::ToBytes, ProtocolVersion}; + +use self::{ + ipc::{ + BidStateRequest, BidStateResponse, CommitRequest, CommitResponse, DistributeRewardsRequest, + DistributeRewardsResponse, ExecuteResponse, GenesisResponse, QueryResponse, SlashRequest, + SlashResponse, UnbondPayoutRequest, UnbondPayoutResponse, UpgradeRequest, UpgradeResponse, + }, + ipc_grpc::{ExecutionEngineService, ExecutionEngineServiceServer}, + mappings::{ParsingError, TransformMap}, +}; + +const METRIC_DURATION_COMMIT: &str = "commit_duration"; +const METRIC_DURATION_EXEC: &str = "exec_duration"; +const METRIC_DURATION_QUERY: &str = "query_duration"; +const METRIC_DURATION_GENESIS: &str = "genesis_duration"; +const METRIC_DURATION_UPGRADE: &str = "upgrade_duration"; + +const TAG_RESPONSE_COMMIT: &str = "commit_response"; +const TAG_RESPONSE_EXEC: &str = "exec_response"; +const TAG_RESPONSE_QUERY: &str = "query_response"; +const TAG_RESPONSE_GENESIS: &str = "genesis_response"; +const TAG_RESPONSE_UPGRADE: &str = "upgrade_response"; + +const UNIMPLEMENTED: &str = "unimplemented"; + +const DEFAULT_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::V1_0_0; + +// Idea is that Engine will represent the core of the execution engine project. +// It will act as an entry point for execution of Wasm binaries. +// Proto definitions should be translated into domain objects when Engine's API +// is invoked. This way core won't depend on casperlabs-engine-grpc-server +// (outer layer) leading to cleaner design. +impl ExecutionEngineService for EngineState +where + S: StateProvider, + EngineError: From, + S::Error: Into + Debug, +{ + fn query( + &self, + _request_options: RequestOptions, + query_request: ipc::QueryRequest, + ) -> SingleResponse { + let start = Instant::now(); + let correlation_id = CorrelationId::new(); + + let request: QueryRequest = match query_request.try_into() { + Ok(ret) => ret, + Err(err) => { + let log_message = format!("{:?}", err); + warn!("{}", log_message); + let mut result = ipc::QueryResponse::new(); + result.set_failure(log_message); + log_duration( + correlation_id, + METRIC_DURATION_QUERY, + TAG_RESPONSE_QUERY, + start.elapsed(), + ); + return SingleResponse::completed(result); + } + }; + + let result = self.run_query(correlation_id, request); + + let response = match result { + Ok(QueryResult::Success(value)) => { + let mut result = ipc::QueryResponse::new(); + match value.to_bytes() { + Ok(serialized_value) => { + info!("query successful; correlation_id: {}", correlation_id); + result.set_success(serialized_value); + } + Err(error_msg) => { + let log_message = format!("Failed to serialize StoredValue: {}", error_msg); + warn!("{}", log_message); + result.set_failure(log_message); + } + } + result + } + Ok(QueryResult::ValueNotFound(msg)) => { + info!("{}", msg); + let mut result = ipc::QueryResponse::new(); + result.set_failure(msg); + result + } + Ok(QueryResult::RootNotFound) => { + let log_message = "Root not found"; + info!("{}", log_message); + let mut result = ipc::QueryResponse::new(); + result.set_failure(log_message.to_string()); + result + } + Ok(QueryResult::CircularReference(msg)) => { + warn!("{}", msg); + let mut result = ipc::QueryResponse::new(); + result.set_failure(msg); + result + } + Err(err) => { + let log_message = format!("{:?}", err); + warn!("{}", log_message); + let mut result = ipc::QueryResponse::new(); + result.set_failure(log_message); + result + } + }; + + log_duration( + correlation_id, + METRIC_DURATION_QUERY, + TAG_RESPONSE_QUERY, + start.elapsed(), + ); + + SingleResponse::completed(response) + } + + fn execute( + &self, + _request_options: RequestOptions, + exec_request: ipc::ExecuteRequest, + ) -> SingleResponse { + let start = Instant::now(); + let correlation_id = CorrelationId::new(); + + let exec_request: ExecuteRequest = match exec_request.try_into() { + Ok(ret) => ret, + Err(err) => { + return SingleResponse::completed(err); + } + }; + + let mut exec_response = ExecuteResponse::new(); + + let results = match self.run_execute(correlation_id, exec_request) { + Ok(results) => results, + Err(error) => { + info!("deploy results error: RootNotFound"); + exec_response.mut_missing_parent().set_hash(error.to_vec()); + log_duration( + correlation_id, + METRIC_DURATION_EXEC, + TAG_RESPONSE_EXEC, + start.elapsed(), + ); + return SingleResponse::completed(exec_response); + } + }; + + let protobuf_results_iter = results.into_iter().map(Into::into); + exec_response + .mut_success() + .set_deploy_results(FromIterator::from_iter(protobuf_results_iter)); + log_duration( + correlation_id, + METRIC_DURATION_EXEC, + TAG_RESPONSE_EXEC, + start.elapsed(), + ); + SingleResponse::completed(exec_response) + } + + fn commit( + &self, + _request_options: RequestOptions, + mut commit_request: CommitRequest, + ) -> SingleResponse { + let start = Instant::now(); + let correlation_id = CorrelationId::new(); + + // TODO + let protocol_version = { + let protocol_version = commit_request.take_protocol_version().into(); + if protocol_version < DEFAULT_PROTOCOL_VERSION { + DEFAULT_PROTOCOL_VERSION + } else { + protocol_version + } + }; + + // Acquire pre-state hash + let pre_state_hash: Blake2bHash = match commit_request.get_prestate_hash().try_into() { + Err(_) => { + let error_message = "Could not parse pre-state hash".to_string(); + warn!("{}", error_message); + let mut commit_response = CommitResponse::new(); + commit_response + .mut_failed_transform() + .set_message(error_message); + return SingleResponse::completed(commit_response); + } + Ok(hash) => hash, + }; + + // Acquire commit transforms + let transforms = match TransformMap::try_from(commit_request.take_effects().into_vec()) { + Err(ParsingError(error_message)) => { + warn!("{}", error_message); + let mut commit_response = CommitResponse::new(); + commit_response + .mut_failed_transform() + .set_message(error_message); + return SingleResponse::completed(commit_response); + } + Ok(transforms) => transforms.into_inner(), + }; + + // "Apply" effects to global state + let commit_response = { + let mut ret = CommitResponse::new(); + + match self.apply_effect(correlation_id, protocol_version, pre_state_hash, transforms) { + Ok(CommitResult::Success { + state_root, + bonded_validators, + }) => { + let properties = { + let mut tmp = BTreeMap::new(); + tmp.insert("post-state-hash", format!("{:?}", state_root)); + tmp.insert("success", true.to_string()); + tmp + }; + logging::log_details( + Level::Info, + "effects applied; new state hash is: {post-state-hash}".to_owned(), + properties, + ); + + let bonds = bonded_validators.into_iter().map(Into::into).collect(); + let commit_result = ret.mut_success(); + commit_result.set_poststate_hash(state_root.to_vec()); + commit_result.set_bonded_validators(bonds); + } + Ok(CommitResult::RootNotFound) => { + warn!("RootNotFound"); + ret.mut_missing_prestate().set_hash(pre_state_hash.to_vec()); + } + Ok(CommitResult::KeyNotFound(key)) => { + warn!("{:?} not found", key); + ret.set_key_not_found(key.into()); + } + Ok(CommitResult::TypeMismatch(type_mismatch)) => { + warn!("{:?}", type_mismatch); + ret.set_type_mismatch(type_mismatch.into()); + } + Ok(CommitResult::Serialization(error)) => { + warn!("{:?}", error); + ret.mut_failed_transform() + .set_message(format!("{:?}", error)); + } + Err(error) => { + warn!("State error {:?} when applying transforms", error); + ret.mut_failed_transform() + .set_message(format!("{:?}", error)); + } + } + + ret + }; + + log_duration( + correlation_id, + METRIC_DURATION_COMMIT, + TAG_RESPONSE_COMMIT, + start.elapsed(), + ); + + SingleResponse::completed(commit_response) + } + + fn run_genesis( + &self, + _request_options: RequestOptions, + run_genesis_request: ipc::RunGenesisRequest, + ) -> SingleResponse { + let start = Instant::now(); + let correlation_id = CorrelationId::new(); + + let run_genesis_request: RunGenesisRequest = match run_genesis_request.try_into() { + Ok(genesis_config) => genesis_config, + Err(error) => { + let err_msg = error.to_string(); + warn!("{}", err_msg); + + let mut genesis_response = GenesisResponse::new(); + genesis_response.mut_failed_deploy().set_message(err_msg); + return SingleResponse::completed(genesis_response); + } + }; + let genesis_config_hash = run_genesis_request.genesis_config_hash(); + let protocol_version = run_genesis_request.protocol_version(); + let ee_config = run_genesis_request.ee_config(); + + let genesis_response = match self.commit_genesis( + correlation_id, + genesis_config_hash, + protocol_version, + ee_config, + ) { + Ok(GenesisResult::Success { + post_state_hash, + effect, + }) => { + let success_message = format!("run_genesis successful: {}", post_state_hash); + info!("{}", success_message); + + let mut genesis_response = GenesisResponse::new(); + let genesis_result = genesis_response.mut_success(); + genesis_result.set_poststate_hash(post_state_hash.to_vec()); + genesis_result.set_effect(effect.into()); + genesis_response + } + Ok(genesis_result) => { + let err_msg = genesis_result.to_string(); + warn!("{}", err_msg); + + let mut genesis_response = GenesisResponse::new(); + genesis_response.mut_failed_deploy().set_message(err_msg); + genesis_response + } + Err(err) => { + let err_msg = err.to_string(); + warn!("{}", err_msg); + + let mut genesis_response = GenesisResponse::new(); + genesis_response.mut_failed_deploy().set_message(err_msg); + genesis_response + } + }; + + log_duration( + correlation_id, + METRIC_DURATION_GENESIS, + TAG_RESPONSE_GENESIS, + start.elapsed(), + ); + + SingleResponse::completed(genesis_response) + } + + fn upgrade( + &self, + _request_options: RequestOptions, + upgrade_request: UpgradeRequest, + ) -> SingleResponse { + let start = Instant::now(); + let correlation_id = CorrelationId::new(); + + let upgrade_config: UpgradeConfig = match upgrade_request.try_into() { + Ok(upgrade_config) => upgrade_config, + Err(error) => { + let err_msg = error.to_string(); + warn!("{}", err_msg); + + let mut upgrade_response = UpgradeResponse::new(); + upgrade_response.mut_failed_deploy().set_message(err_msg); + + log_duration( + correlation_id, + METRIC_DURATION_UPGRADE, + TAG_RESPONSE_UPGRADE, + start.elapsed(), + ); + + return SingleResponse::completed(upgrade_response); + } + }; + + let upgrade_response = match self.commit_upgrade(correlation_id, upgrade_config) { + Ok(UpgradeResult::Success { + post_state_hash, + effect, + }) => { + info!("upgrade successful: {}", post_state_hash); + let mut ret = UpgradeResponse::new(); + let upgrade_result = ret.mut_success(); + upgrade_result.set_post_state_hash(post_state_hash.to_vec()); + upgrade_result.set_effect(effect.into()); + ret + } + Ok(upgrade_result) => { + let err_msg = upgrade_result.to_string(); + warn!("{}", err_msg); + + let mut ret = UpgradeResponse::new(); + ret.mut_failed_deploy().set_message(err_msg); + ret + } + Err(err) => { + let err_msg = err.to_string(); + warn!("{}", err_msg); + + let mut ret = UpgradeResponse::new(); + ret.mut_failed_deploy().set_message(err_msg); + ret + } + }; + + log_duration( + correlation_id, + METRIC_DURATION_UPGRADE, + TAG_RESPONSE_UPGRADE, + start.elapsed(), + ); + + SingleResponse::completed(upgrade_response) + } + + fn bid_state( + &self, + _request_options: RequestOptions, + _bid_state_request: BidStateRequest, + ) -> SingleResponse { + SingleResponse::err(GrpcError::Panic(UNIMPLEMENTED.to_string())) + } + + fn distribute_rewards( + &self, + _request_options: RequestOptions, + _distribute_rewards_request: DistributeRewardsRequest, + ) -> SingleResponse { + SingleResponse::err(GrpcError::Panic(UNIMPLEMENTED.to_string())) + } + + fn slash( + &self, + _request_options: RequestOptions, + _slash_request: SlashRequest, + ) -> SingleResponse { + SingleResponse::err(GrpcError::Panic(UNIMPLEMENTED.to_string())) + } + + fn unbond_payout( + &self, + _request_options: RequestOptions, + _unbond_payout_request: UnbondPayoutRequest, + ) -> SingleResponse { + SingleResponse::err(GrpcError::Panic(UNIMPLEMENTED.to_string())) + } +} + +// Helper method which returns single DeployResult that is set to be a +// WasmError. +pub fn new( + socket: &str, + thread_count: usize, + e: E, +) -> ServerBuilder { + let socket_path = std::path::Path::new(socket); + + if let Err(e) = std::fs::remove_file(socket_path) { + if e.kind() != ErrorKind::NotFound { + panic!("failed to remove old socket file: {:?}", e); + } + } + + let mut server = ServerBuilder::new_plain(); + server.http.set_unix_addr(socket.to_owned()).unwrap(); + server.http.set_cpu_pool_threads(thread_count); + server.add_service(ExecutionEngineServiceServer::new_service_def(e)); + server +} diff --git a/grpc/server/src/lib.rs b/grpc/server/src/lib.rs new file mode 100644 index 0000000000..ad8b1139f5 --- /dev/null +++ b/grpc/server/src/lib.rs @@ -0,0 +1 @@ +pub mod engine_server; diff --git a/grpc/server/src/main.rs b/grpc/server/src/main.rs new file mode 100644 index 0000000000..635b4b830b --- /dev/null +++ b/grpc/server/src/main.rs @@ -0,0 +1,393 @@ +use std::{ + collections::BTreeMap, + fs, + path::PathBuf, + str::FromStr, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, +}; + +use clap::{App, Arg, ArgMatches}; +use dirs::home_dir; +use lmdb::DatabaseFlags; +use log::{error, info, Level, LevelFilter}; +use node::contract_core::engine_state::{EngineConfig, EngineState}; + +use node::contract_shared::{ + logging::{self, Settings, Style}, + page_size, socket, +}; +use node::contract_storage::{ + global_state::lmdb::LmdbGlobalState, transaction_source::lmdb::LmdbEnvironment, + trie_store::lmdb::LmdbTrieStore, +}; + +use casperlabs_engine_grpc_server::engine_server; +use node::contract_storage::protocol_data_store::lmdb::LmdbProtocolDataStore; + +// exe / proc +const PROC_NAME: &str = "casperlabs-engine-grpc-server"; +const APP_NAME: &str = "CasperLabs Execution Engine Server"; +const SERVER_LISTENING_TEMPLATE: &str = "{listener} is listening on socket: {socket}"; +const SERVER_START_EXPECT: &str = "failed to start Execution Engine Server"; + +// data-dir / lmdb +const ARG_DATA_DIR: &str = "data-dir"; +const ARG_DATA_DIR_SHORT: &str = "d"; +const ARG_DATA_DIR_VALUE: &str = "DIR"; +const ARG_DATA_DIR_HELP: &str = "Sets the data directory"; +const DEFAULT_DATA_DIR_RELATIVE: &str = ".casperlabs"; +const GLOBAL_STATE_DIR: &str = "global_state"; +const GET_HOME_DIR_EXPECT: &str = "Could not get home directory"; +const CREATE_DATA_DIR_EXPECT: &str = "Could not create directory"; +const LMDB_ENVIRONMENT_EXPECT: &str = "Could not create LmdbEnvironment"; +const LMDB_TRIE_STORE_EXPECT: &str = "Could not create LmdbTrieStore"; +const LMDB_PROTOCOL_DATA_STORE_EXPECT: &str = "Could not create LmdbProtocolDataStore"; +const LMDB_GLOBAL_STATE_EXPECT: &str = "Could not create LmdbGlobalState"; + +// pages / lmdb +const ARG_PAGES: &str = "pages"; +const ARG_PAGES_SHORT: &str = "p"; +const ARG_PAGES_VALUE: &str = "NUM"; +const ARG_PAGES_HELP: &str = "Sets the max number of pages to use for lmdb's mmap"; +const GET_PAGES_EXPECT: &str = "Could not parse pages argument"; +// 750 GiB = 805306368000 bytes +// page size on x86_64 linux = 4096 bytes +// 805306368000 / 4096 = 196608000 +const DEFAULT_PAGES: usize = 196_608_000; + +// socket +const ARG_SOCKET: &str = "socket"; +const ARG_SOCKET_HELP: &str = + "Path to socket. Note that this path is independent of the data directory."; +const ARG_SOCKET_EXPECT: &str = "socket required"; + +// log level +const ARG_LOG_LEVEL: &str = "log-level"; +const ARG_LOG_LEVEL_VALUE: &str = "LEVEL"; +const ARG_LOG_LEVEL_HELP: &str = "Sets the max logging level"; +const LOG_LEVEL_OFF: &str = "off"; +const LOG_LEVEL_ERROR: &str = "error"; +const LOG_LEVEL_WARN: &str = "warn"; +const LOG_LEVEL_INFO: &str = "info"; +const LOG_LEVEL_DEBUG: &str = "debug"; +const LOG_LEVEL_TRACE: &str = "trace"; + +// metrics +const ARG_LOG_METRICS: &str = "log-metrics"; +const ARG_LOG_METRICS_HELP: &str = "Enables logging of metrics regardless of log-level setting"; + +// log style +const ARG_LOG_STYLE: &str = "log-style"; +const ARG_LOG_STYLE_VALUE: &str = "STYLE"; +const ARG_LOG_STYLE_HELP: &str = "Sets logging style to structured or human-readable"; +const LOG_STYLE_STRUCTURED: &str = "structured"; +const LOG_STYLE_HUMAN_READABLE: &str = "human"; + +// thread count +const ARG_THREAD_COUNT: &str = "threads"; +const ARG_THREAD_COUNT_SHORT: &str = "t"; +const ARG_THREAD_COUNT_DEFAULT: &str = "1"; +const ARG_THREAD_COUNT_VALUE: &str = "NUM"; +const ARG_THREAD_COUNT_HELP: &str = "Worker thread count"; +const ARG_THREAD_COUNT_EXPECT: &str = "expected valid thread count"; + +// use system contracts +const ARG_USE_SYSTEM_CONTRACTS: &str = "use-system-contracts"; +const ARG_USE_SYSTEM_CONTRACTS_SHORT: &str = "z"; +const ARG_USE_SYSTEM_CONTRACTS_HELP: &str = + "Use system contracts instead of host-side logic for Mint, Proof of Stake and Standard Payment"; + +// Highway +const ARG_ENABLE_BONDING: &str = "enable-bonding"; +const ARG_ENABLE_BONDING_SHORT: &str = "b"; +const ARG_ENABLE_BONDING_HELP: &str = "Enable bonding"; + +// runnable +const SIGINT_HANDLE_EXPECT: &str = "Error setting Ctrl-C handler"; +const RUNNABLE_CHECK_INTERVAL_SECONDS: u64 = 3; + +fn main() { + set_panic_hook(); + + let arg_matches = get_args(); + + let _ = logging::initialize(get_log_settings(&arg_matches)); + + info!("starting Execution Engine Server"); + + let socket = get_socket(&arg_matches); + + match socket.remove_file() { + Err(e) => panic!("failed to remove old socket file: {:?}", e), + Ok(_) => info!("removing old socket file"), + }; + + let data_dir = get_data_dir(&arg_matches); + + let map_size = get_map_size(&arg_matches); + + let thread_count = get_thread_count(&arg_matches); + + let engine_config: EngineConfig = get_engine_config(&arg_matches); + + let _server = get_grpc_server(&socket, data_dir, map_size, thread_count, engine_config); + + log_listening_message(&socket); + + let interval = Duration::from_secs(RUNNABLE_CHECK_INTERVAL_SECONDS); + + let runnable = get_sigint_handle(); + + while runnable.load(Ordering::SeqCst) { + std::thread::park_timeout(interval); + } + + info!("stopping Execution Engine Server"); +} + +/// Sets panic hook for logging panic info +fn set_panic_hook() { + let hook: Box = Box::new( + move |panic_info| match panic_info.payload().downcast_ref::<&str>() { + Some(s) => { + error!("{:?}", s); + } + None => { + error!("{:?}", panic_info); + } + }, + ); + std::panic::set_hook(hook); +} + +/// Gets command line arguments +fn get_args() -> ArgMatches<'static> { + App::new(APP_NAME) + .version(env!("CARGO_PKG_VERSION")) + .arg( + Arg::with_name(ARG_LOG_LEVEL) + .required(false) + .long(ARG_LOG_LEVEL) + .takes_value(true) + .possible_value(LOG_LEVEL_OFF) + .possible_value(LOG_LEVEL_ERROR) + .possible_value(LOG_LEVEL_WARN) + .possible_value(LOG_LEVEL_INFO) + .possible_value(LOG_LEVEL_DEBUG) + .possible_value(LOG_LEVEL_TRACE) + .default_value(LOG_LEVEL_INFO) + .value_name(ARG_LOG_LEVEL_VALUE) + .help(ARG_LOG_LEVEL_HELP), + ) + .arg( + Arg::with_name(ARG_LOG_METRICS) + .required(false) + .long(ARG_LOG_METRICS) + .takes_value(false) + .help(ARG_LOG_METRICS_HELP), + ) + .arg( + Arg::with_name(ARG_LOG_STYLE) + .required(false) + .long(ARG_LOG_STYLE) + .takes_value(true) + .possible_value(LOG_STYLE_STRUCTURED) + .possible_value(LOG_STYLE_HUMAN_READABLE) + .default_value(LOG_STYLE_STRUCTURED) + .value_name(ARG_LOG_STYLE_VALUE) + .help(ARG_LOG_STYLE_HELP), + ) + .arg( + Arg::with_name(ARG_DATA_DIR) + .short(ARG_DATA_DIR_SHORT) + .long(ARG_DATA_DIR) + .value_name(ARG_DATA_DIR_VALUE) + .help(ARG_DATA_DIR_HELP) + .takes_value(true), + ) + .arg( + Arg::with_name(ARG_PAGES) + .short(ARG_PAGES_SHORT) + .long(ARG_PAGES) + .value_name(ARG_PAGES_VALUE) + .help(ARG_PAGES_HELP) + .takes_value(true), + ) + .arg( + Arg::with_name(ARG_THREAD_COUNT) + .short(ARG_THREAD_COUNT_SHORT) + .long(ARG_THREAD_COUNT) + .takes_value(true) + .default_value(ARG_THREAD_COUNT_DEFAULT) + .value_name(ARG_THREAD_COUNT_VALUE) + .help(ARG_THREAD_COUNT_HELP), + ) + .arg( + Arg::with_name(ARG_USE_SYSTEM_CONTRACTS) + .short(ARG_USE_SYSTEM_CONTRACTS_SHORT) + .long(ARG_USE_SYSTEM_CONTRACTS) + .help(ARG_USE_SYSTEM_CONTRACTS_HELP), + ) + .arg( + Arg::with_name(ARG_ENABLE_BONDING) + .short(ARG_ENABLE_BONDING_SHORT) + .long(ARG_ENABLE_BONDING) + .help(ARG_ENABLE_BONDING_HELP), + ) + .arg( + Arg::with_name(ARG_SOCKET) + .required(true) + .help(ARG_SOCKET_HELP) + .index(1), + ) + .get_matches() +} + +/// Gets SIGINT handle to allow clean exit +fn get_sigint_handle() -> Arc { + let handle = Arc::new(AtomicBool::new(true)); + let h = handle.clone(); + ctrlc::set_handler(move || { + h.store(false, Ordering::SeqCst); + }) + .expect(SIGINT_HANDLE_EXPECT); + handle +} + +/// Gets value of socket argument +fn get_socket(arg_matches: &ArgMatches) -> socket::Socket { + let socket = arg_matches.value_of(ARG_SOCKET).expect(ARG_SOCKET_EXPECT); + + socket::Socket::new(socket.to_owned()) +} + +/// Gets value of data-dir argument +fn get_data_dir(arg_matches: &ArgMatches) -> PathBuf { + let mut buf = arg_matches.value_of(ARG_DATA_DIR).map_or( + { + let mut dir = home_dir().expect(GET_HOME_DIR_EXPECT); + dir.push(DEFAULT_DATA_DIR_RELATIVE); + dir + }, + PathBuf::from, + ); + buf.push(GLOBAL_STATE_DIR); + fs::create_dir_all(&buf).unwrap_or_else(|_| panic!("{}: {:?}", CREATE_DATA_DIR_EXPECT, buf)); + buf +} + +/// Parses pages argument and returns map size +fn get_map_size(arg_matches: &ArgMatches) -> usize { + let page_size = page_size::get_page_size().unwrap(); + let pages = arg_matches + .value_of(ARG_PAGES) + .map_or(Ok(DEFAULT_PAGES), usize::from_str) + .expect(GET_PAGES_EXPECT); + page_size * pages +} + +fn get_thread_count(arg_matches: &ArgMatches) -> usize { + arg_matches + .value_of(ARG_THREAD_COUNT) + .map(str::parse) + .expect(ARG_THREAD_COUNT_EXPECT) + .expect(ARG_THREAD_COUNT_EXPECT) +} + +/// Returns an [`EngineConfig`]. +fn get_engine_config(arg_matches: &ArgMatches) -> EngineConfig { + // feature flags go here + let use_system_contracts = arg_matches.is_present(ARG_USE_SYSTEM_CONTRACTS); + let enable_bonding = arg_matches.is_present(ARG_ENABLE_BONDING); + EngineConfig::new() + .with_use_system_contracts(use_system_contracts) + .with_enable_bonding(enable_bonding) +} + +/// Builds and returns a gRPC server. +fn get_grpc_server( + socket: &socket::Socket, + data_dir: PathBuf, + map_size: usize, + thread_count: usize, + engine_config: EngineConfig, +) -> grpc::Server { + let engine_state = get_engine_state(data_dir, map_size, engine_config); + + engine_server::new(socket.as_str(), thread_count, engine_state) + .build() + .expect(SERVER_START_EXPECT) +} + +/// Builds and returns engine global state +fn get_engine_state( + data_dir: PathBuf, + map_size: usize, + engine_config: EngineConfig, +) -> EngineState { + let environment = { + let ret = LmdbEnvironment::new(&data_dir, map_size).expect(LMDB_ENVIRONMENT_EXPECT); + Arc::new(ret) + }; + + let trie_store = { + let ret = LmdbTrieStore::new(&environment, None, DatabaseFlags::empty()) + .expect(LMDB_TRIE_STORE_EXPECT); + Arc::new(ret) + }; + + let protocol_data_store = { + let ret = LmdbProtocolDataStore::new(&environment, None, DatabaseFlags::empty()) + .expect(LMDB_PROTOCOL_DATA_STORE_EXPECT); + Arc::new(ret) + }; + + let global_state = LmdbGlobalState::empty(environment, trie_store, protocol_data_store) + .expect(LMDB_GLOBAL_STATE_EXPECT); + + EngineState::new(global_state, engine_config) +} + +/// Builds and returns log settings +fn get_log_settings(arg_matches: &ArgMatches) -> Settings { + let max_level = match arg_matches + .value_of(ARG_LOG_LEVEL) + .expect("should have default value if not explicitly set") + { + LOG_LEVEL_OFF => LevelFilter::Off, + LOG_LEVEL_ERROR => LevelFilter::Error, + LOG_LEVEL_WARN => LevelFilter::Warn, + LOG_LEVEL_INFO => LevelFilter::Info, + LOG_LEVEL_DEBUG => LevelFilter::Debug, + LOG_LEVEL_TRACE => LevelFilter::Trace, + _ => unreachable!("should validate log-level arg to match one of the options"), + }; + + let enable_metrics = arg_matches.is_present(ARG_LOG_METRICS); + + let style = match arg_matches.value_of(ARG_LOG_STYLE) { + Some(LOG_STYLE_HUMAN_READABLE) => Style::HumanReadable, + _ => Style::Structured, + }; + + Settings::new(max_level) + .with_metrics_enabled(enable_metrics) + .with_style(style) +} + +/// Logs listening on socket message +fn log_listening_message(socket: &socket::Socket) { + let mut properties = BTreeMap::new(); + properties.insert("listener", PROC_NAME.to_owned()); + properties.insert("socket", socket.value()); + + logging::log_details( + Level::Info, + (&*SERVER_LISTENING_TEMPLATE).to_string(), + properties, + ); +} diff --git a/grpc/test-support/Cargo.toml b/grpc/test-support/Cargo.toml new file mode 100644 index 0000000000..ec6ce6d2d9 --- /dev/null +++ b/grpc/test-support/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "casperlabs-engine-test-support" +version = "0.8.0" # when updating, also update 'html_root_url' in lib.rs +authors = ["Fraser Hutchison "] +edition = "2018" +description = "Library to support testing of Wasm smart contracts for use on the CasperLabs network." +readme = "README.md" +documentation = "https://docs.rs/casperlabs-engine-test-support" +homepage = "https://casperlabs.io" +repository = "https://github.com/CasperLabs/CasperLabs/tree/master/execution-engine/engine-test-support" +license-file = "../../LICENSE" + +[dependencies] +contract = { version = "0.6.0", path = "../../smart-contracts/contract", package = "casperlabs-contract" } +engine-grpc-server = { version = "0.20.0", path = "../server", package = "casperlabs-engine-grpc-server" } +node = { path = "../../node", package = "casperlabs-node" } +grpc = "0.6.1" +lazy_static = "1" +lmdb = "0.8.0" +log = "0.4.8" +num-traits = "0.2.10" +rand = "0.7.2" +protobuf = "=2.8" +types = { version = "0.6.0", path = "../../types", package = "casperlabs-types", features = ["std"] } + +[dev-dependencies] +version-sync = "0.8" + +[features] +enable-bonding = [] +use-as-wasm = [] +use-system-contracts = [] +test-support = ["engine-grpc-server/test-support", "contract/test-support"] +no-unstable-features = [ + "contract/no-unstable-features", + "engine-grpc-server/no-unstable-features", + "types/no-unstable-features" +] + +[package.metadata.docs.rs] +features = ["no-unstable-features"] diff --git a/grpc/test-support/README.md b/grpc/test-support/README.md new file mode 100644 index 0000000000..946fa22801 --- /dev/null +++ b/grpc/test-support/README.md @@ -0,0 +1,14 @@ +# `casperlabs-engine-test-support` + +[![LOGO](https://raw.githubusercontent.com/CasperLabs/CasperLabs/master/CasperLabs_Logo_Horizontal_RGB.png)](https://casperlabs.io/) + +[![Build Status](https://drone-auto.casperlabs.io/api/badges/CasperLabs/CasperLabs/status.svg?branch=dev)](http://drone-auto.casperlabs.io/CasperLabs/CasperLabs) +[![Crates.io](https://img.shields.io/crates/v/casperlabs-engine-test-support)](https://crates.io/crates/casperlabs-engine-test-support) +[![Documentation](https://docs.rs/casperlabs-engine-test-support/badge.svg)](https://docs.rs/casperlabs-engine-test-support) +[![License](https://img.shields.io/badge/license-COSL-blue.svg)](https://github.com/CasperLabs/CasperLabs/blob/master/LICENSE) + +A library to support testing of Wasm smart contracts for use on the CasperLabs network. + +## License + +Licensed under the [CasperLabs Open Source License (COSL)](https://github.com/CasperLabs/CasperLabs/blob/master/LICENSE). diff --git a/grpc/test-support/src/account.rs b/grpc/test-support/src/account.rs new file mode 100644 index 0000000000..4eaeb51951 --- /dev/null +++ b/grpc/test-support/src/account.rs @@ -0,0 +1,52 @@ +use std::convert::TryFrom; + +use node::contract_shared; +use node::contract_shared::stored_value::StoredValue; +use types::{account::AccountHash, contracts::NamedKeys, URef}; + +use crate::{Error, Result}; + +/// An `Account` instance. +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct Account { + inner: contract_shared::account::Account, +} + +impl Account { + /// creates a new Account instance. + pub(crate) fn new(account: contract_shared::account::Account) -> Self { + Account { inner: account } + } + + /// Returns the public_key. + pub fn account_hash(&self) -> AccountHash { + self.inner.account_hash() + } + + /// Returns the named_keys. + pub fn named_keys(&self) -> &NamedKeys { + self.inner.named_keys() + } + + /// Returns the main_purse. + pub fn main_purse(&self) -> URef { + self.inner.main_purse() + } +} + +impl From for Account { + fn from(value: contract_shared::account::Account) -> Self { + Account::new(value) + } +} + +impl TryFrom for Account { + type Error = Error; + + fn try_from(value: StoredValue) -> Result { + match value { + StoredValue::Account(account) => Ok(Account::new(account)), + _ => Err(Error::from(String::from("StoredValue is not an Account"))), + } + } +} diff --git a/grpc/test-support/src/code.rs b/grpc/test-support/src/code.rs new file mode 100644 index 0000000000..0aac14854a --- /dev/null +++ b/grpc/test-support/src/code.rs @@ -0,0 +1,34 @@ +use std::path::{Path, PathBuf}; + +use crate::Hash; + +/// Represents the types of session or payment code. +pub enum Code { + /// The filesystem path of compiled Wasm code. + Path(PathBuf), + /// A named key providing the location of a stored contract. + NamedKey(String, String), + /// A hash providing the location of a stored contract. + Hash(Hash, String), +} + +// Note: can't just `impl> From for Code` because the compiler complains about +// a conflicting implementation of `From` - as URef could be made `AsRef` in the future + +impl<'a> From<&'a str> for Code { + fn from(path: &'a str) -> Code { + Code::Path(path.into()) + } +} + +impl<'a> From<&'a Path> for Code { + fn from(path: &'a Path) -> Code { + Code::Path(path.into()) + } +} + +impl From for Code { + fn from(path: PathBuf) -> Code { + Code::Path(path) + } +} diff --git a/grpc/test-support/src/error.rs b/grpc/test-support/src/error.rs new file mode 100644 index 0000000000..7b582a1083 --- /dev/null +++ b/grpc/test-support/src/error.rs @@ -0,0 +1,35 @@ +use std::result; + +use node::contract_shared::TypeMismatch; +use types::CLValueError; + +/// The error type returned by any casperlabs-engine-test-support operation. +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Debug)] +pub struct Error { + inner: String, +} + +impl From for Error { + fn from(error: String) -> Self { + Error { inner: error } + } +} + +impl From for Error { + fn from(error: CLValueError) -> Self { + Error { + inner: format!("{:?}", error), + } + } +} + +impl From for Error { + fn from(error: TypeMismatch) -> Self { + Error { + inner: format!("{:?}", error), + } + } +} + +/// A specialized `std::result::Result` for this crate. +pub type Result = result::Result; diff --git a/grpc/test-support/src/internal/additive_map_diff.rs b/grpc/test-support/src/internal/additive_map_diff.rs new file mode 100644 index 0000000000..c3d0e598d1 --- /dev/null +++ b/grpc/test-support/src/internal/additive_map_diff.rs @@ -0,0 +1,195 @@ +use node::contract_shared::{additive_map::AdditiveMap, transform::Transform}; +use types::Key; + +/// Represents the difference between two `AdditiveMap`s. +#[derive(Debug, Default, PartialEq, Eq)] +pub struct AdditiveMapDiff { + left: AdditiveMap, + both: AdditiveMap, + right: AdditiveMap, +} + +impl AdditiveMapDiff { + /// Creates a diff from two `AdditiveMap`s. + pub fn new( + mut left: AdditiveMap, + mut right: AdditiveMap, + ) -> Self { + let mut both = AdditiveMap::new(); + for key in left.keys().copied().collect::>() { + // Safe to unwrap here since we're iterating `left` keys, so `left.remove` must succeed. + let left_value = left.remove(&key).unwrap(); + if let Some(right_value) = right.remove(&key) { + if left_value == right_value { + both.insert(key, left_value); + } else { + left.insert(key, left_value); + right.insert(key, right_value); + } + } else { + left.insert(key, left_value); + } + } + + AdditiveMapDiff { left, both, right } + } + + /// Returns the entries that are unique to the `left` input. + pub fn left(&self) -> &AdditiveMap { + &self.left + } + + /// Returns the entries that are unique to the `right` input. + pub fn right(&self) -> &AdditiveMap { + &self.right + } + + /// Returns the entries shared by both inputs. + pub fn both(&self) -> &AdditiveMap { + &self.both + } +} + +#[cfg(test)] +mod tests { + use lazy_static::lazy_static; + use rand::{self, Rng}; + + use types::{AccessRights, Key, URef, BLAKE2B_DIGEST_LENGTH}; + + use super::*; + + const MIN_ELEMENTS: u8 = 1; + const MAX_ELEMENTS: u8 = 10; + + lazy_static! { + static ref LEFT_ONLY: AdditiveMap = { + let mut map = AdditiveMap::new(); + for i in 0..random_element_count() { + map.insert( + Key::URef(URef::new( + [i; BLAKE2B_DIGEST_LENGTH], + AccessRights::READ_ADD_WRITE, + )), + Transform::AddInt32(i.into()), + ); + } + map + }; + static ref BOTH: AdditiveMap = { + let mut map = AdditiveMap::new(); + for i in 0..random_element_count() { + map.insert( + Key::URef(URef::new( + [i + MAX_ELEMENTS; BLAKE2B_DIGEST_LENGTH], + AccessRights::READ_ADD_WRITE, + )), + Transform::Identity, + ); + } + map + }; + static ref RIGHT_ONLY: AdditiveMap = { + let mut map = AdditiveMap::new(); + for i in 0..random_element_count() { + map.insert( + Key::URef(URef::new( + [i; BLAKE2B_DIGEST_LENGTH], + AccessRights::READ_ADD_WRITE, + )), + Transform::AddUInt512(i.into()), + ); + } + map + }; + } + + fn random_element_count() -> u8 { + rand::thread_rng().gen_range(MIN_ELEMENTS, MAX_ELEMENTS + 1) + } + + struct TestFixture { + expected: AdditiveMapDiff, + } + + impl TestFixture { + fn new( + left_only: AdditiveMap, + both: AdditiveMap, + right_only: AdditiveMap, + ) -> Self { + TestFixture { + expected: AdditiveMapDiff { + left: left_only, + both, + right: right_only, + }, + } + } + + fn left(&self) -> AdditiveMap { + self.expected + .left + .iter() + .chain(self.expected.both.iter()) + .map(|(key, transform)| (*key, transform.clone())) + .collect() + } + + fn right(&self) -> AdditiveMap { + self.expected + .right + .iter() + .chain(self.expected.both.iter()) + .map(|(key, transform)| (*key, transform.clone())) + .collect() + } + + fn run(&self) { + let diff = AdditiveMapDiff::new(self.left(), self.right()); + assert_eq!(self.expected, diff); + } + } + + #[test] + fn should_create_diff_where_left_is_subset_of_right() { + let fixture = TestFixture::new(AdditiveMap::new(), BOTH.clone(), RIGHT_ONLY.clone()); + fixture.run(); + } + + #[test] + fn should_create_diff_where_right_is_subset_of_left() { + let fixture = TestFixture::new(LEFT_ONLY.clone(), BOTH.clone(), AdditiveMap::new()); + fixture.run(); + } + + #[test] + fn should_create_diff_where_no_intersection() { + let fixture = TestFixture::new(LEFT_ONLY.clone(), AdditiveMap::new(), RIGHT_ONLY.clone()); + fixture.run(); + } + + #[test] + fn should_create_diff_where_both_equal() { + let fixture = TestFixture::new(AdditiveMap::new(), BOTH.clone(), AdditiveMap::new()); + fixture.run(); + } + + #[test] + fn should_create_diff_where_left_is_empty() { + let fixture = TestFixture::new(AdditiveMap::new(), AdditiveMap::new(), RIGHT_ONLY.clone()); + fixture.run(); + } + + #[test] + fn should_create_diff_where_right_is_empty() { + let fixture = TestFixture::new(LEFT_ONLY.clone(), AdditiveMap::new(), AdditiveMap::new()); + fixture.run(); + } + + #[test] + fn should_create_diff_where_both_are_empty() { + let fixture = TestFixture::new(AdditiveMap::new(), AdditiveMap::new(), AdditiveMap::new()); + fixture.run(); + } +} diff --git a/grpc/test-support/src/internal/deploy_item_builder.rs b/grpc/test-support/src/internal/deploy_item_builder.rs new file mode 100644 index 0000000000..dad1d78970 --- /dev/null +++ b/grpc/test-support/src/internal/deploy_item_builder.rs @@ -0,0 +1,249 @@ +use std::{collections::BTreeSet, path::Path}; + +use node::contract_core::{ + engine_state::{deploy_item::DeployItem, executable_deploy_item::ExecutableDeployItem}, + DeployHash, +}; +use types::{ + account::AccountHash, bytesrepr::ToBytes, contracts::ContractVersion, ContractHash, HashAddr, + RuntimeArgs, +}; + +use crate::internal::utils; + +#[derive(Default)] +struct DeployItemData { + pub address: Option, + pub payment_code: Option, + pub session_code: Option, + pub gas_price: u64, + pub authorization_keys: BTreeSet, + pub deploy_hash: DeployHash, +} + +pub struct DeployItemBuilder { + deploy_item: DeployItemData, +} + +impl DeployItemBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn with_address(mut self, address: AccountHash) -> Self { + self.deploy_item.address = Some(address); + self + } + + pub fn with_payment_bytes(mut self, module_bytes: Vec, args: RuntimeArgs) -> Self { + let args = Self::serialize_args(args); + self.deploy_item.payment_code = + Some(ExecutableDeployItem::ModuleBytes { module_bytes, args }); + self + } + + pub fn with_empty_payment_bytes(self, args: RuntimeArgs) -> Self { + self.with_payment_bytes(vec![], args) + } + + pub fn with_payment_code>(self, file_name: T, args: RuntimeArgs) -> Self { + let module_bytes = utils::read_wasm_file_bytes(file_name); + self.with_payment_bytes(module_bytes, args) + } + + pub fn with_stored_payment_hash( + mut self, + hash: ContractHash, + entry_point: &str, + args: RuntimeArgs, + ) -> Self { + let args = Self::serialize_args(args); + self.deploy_item.payment_code = Some(ExecutableDeployItem::StoredContractByHash { + hash, + entry_point: entry_point.into(), + args, + }); + self + } + + pub fn with_stored_payment_named_key( + mut self, + uref_name: &str, + entry_point_name: &str, + args: RuntimeArgs, + ) -> Self { + let args = Self::serialize_args(args); + self.deploy_item.payment_code = Some(ExecutableDeployItem::StoredContractByName { + name: uref_name.to_owned(), + entry_point: entry_point_name.into(), + args, + }); + self + } + + pub fn with_session_bytes(mut self, module_bytes: Vec, args: RuntimeArgs) -> Self { + let args = Self::serialize_args(args); + self.deploy_item.session_code = + Some(ExecutableDeployItem::ModuleBytes { module_bytes, args }); + self + } + + pub fn with_session_code>(self, file_name: T, args: RuntimeArgs) -> Self { + let module_bytes = utils::read_wasm_file_bytes(file_name); + self.with_session_bytes(module_bytes, args) + } + + pub fn with_transfer_args(mut self, args: RuntimeArgs) -> Self { + let args = Self::serialize_args(args); + self.deploy_item.session_code = Some(ExecutableDeployItem::Transfer { args }); + self + } + + pub fn with_stored_session_hash( + mut self, + hash: ContractHash, + entry_point: &str, + args: RuntimeArgs, + ) -> Self { + let args = Self::serialize_args(args); + self.deploy_item.session_code = Some(ExecutableDeployItem::StoredContractByHash { + hash, + entry_point: entry_point.into(), + args, + }); + self + } + + pub fn with_stored_session_named_key( + mut self, + name: &str, + entry_point: &str, + args: RuntimeArgs, + ) -> Self { + let args = Self::serialize_args(args); + self.deploy_item.session_code = Some(ExecutableDeployItem::StoredContractByName { + name: name.to_owned(), + entry_point: entry_point.into(), + args, + }); + self + } + + pub fn with_stored_versioned_contract_by_name( + mut self, + name: &str, + version: Option, + entry_point: &str, + args: RuntimeArgs, + ) -> Self { + self.deploy_item.session_code = Some(ExecutableDeployItem::StoredVersionedContractByName { + name: name.to_owned(), + version, + entry_point: entry_point.to_owned(), + args: args.to_bytes().expect("should serialize runtime args"), + }); + self + } + + pub fn with_stored_versioned_contract_by_hash( + mut self, + hash: HashAddr, + version: Option, + entry_point: &str, + args: RuntimeArgs, + ) -> Self { + self.deploy_item.session_code = Some(ExecutableDeployItem::StoredVersionedContractByHash { + hash, + version, + entry_point: entry_point.to_owned(), + args: args.to_bytes().expect("should serialize runtime args"), + }); + self + } + + pub fn with_stored_versioned_payment_contract_by_name( + mut self, + key_name: &str, + version: Option, + entry_point: &str, + args: RuntimeArgs, + ) -> Self { + self.deploy_item.payment_code = Some(ExecutableDeployItem::StoredVersionedContractByName { + name: key_name.to_owned(), + version, + entry_point: entry_point.to_owned(), + args: args.to_bytes().expect("should serialize runtime args"), + }); + self + } + + pub fn with_stored_versioned_payment_contract_by_hash( + mut self, + hash: HashAddr, + version: Option, + entry_point: &str, + args: RuntimeArgs, + ) -> Self { + self.deploy_item.payment_code = Some(ExecutableDeployItem::StoredVersionedContractByHash { + hash, + version, + entry_point: entry_point.to_owned(), + args: args.to_bytes().expect("should serialize runtime args"), + }); + self + } + + pub fn with_authorization_keys>( + mut self, + authorization_keys: &[T], + ) -> Self { + self.deploy_item.authorization_keys = authorization_keys + .iter() + .cloned() + .map(|v| v.into()) + .collect(); + self + } + + pub fn with_gas_price(mut self, gas_price: u64) -> Self { + self.deploy_item.gas_price = gas_price; + self + } + + pub fn with_deploy_hash(mut self, hash: [u8; 32]) -> Self { + self.deploy_item.deploy_hash = hash; + self + } + + pub fn build(self) -> DeployItem { + DeployItem { + address: self + .deploy_item + .address + .unwrap_or_else(|| AccountHash::new([0u8; 32])), + session: self + .deploy_item + .session_code + .expect("should have session code"), + payment: self + .deploy_item + .payment_code + .expect("should have payment code"), + gas_price: self.deploy_item.gas_price, + authorization_keys: self.deploy_item.authorization_keys, + deploy_hash: self.deploy_item.deploy_hash, + } + } + + fn serialize_args(args: RuntimeArgs) -> Vec { + args.into_bytes().expect("should serialize args") + } +} + +impl Default for DeployItemBuilder { + fn default() -> Self { + let mut deploy_item: DeployItemData = Default::default(); + deploy_item.gas_price = 1; + DeployItemBuilder { deploy_item } + } +} diff --git a/grpc/test-support/src/internal/exec_with_return.rs b/grpc/test-support/src/internal/exec_with_return.rs new file mode 100644 index 0000000000..a3990f3ea2 --- /dev/null +++ b/grpc/test-support/src/internal/exec_with_return.rs @@ -0,0 +1,172 @@ +use std::{cell::RefCell, collections::BTreeSet, convert::TryInto, rc::Rc}; + +use engine_grpc_server::engine_server::ipc_grpc::ExecutionEngineService; +use node::contract_core::{ + engine_state::{ + executable_deploy_item::ExecutableDeployItem, execution_effect::ExecutionEffect, + EngineConfig, EngineState, + }, + execution::{self, AddressGenerator}, + runtime::{self, Runtime}, + runtime_context::RuntimeContext, +}; +use node::contract_shared::wasm_prep::Preprocessor; +use node::contract_shared::{gas::Gas, newtypes::CorrelationId}; +use node::contract_storage::{global_state::StateProvider, protocol_data::ProtocolData}; +use types::{ + account::AccountHash, bytesrepr::FromBytes, BlockTime, CLTyped, EntryPointType, Key, Phase, + ProtocolVersion, RuntimeArgs, URef, U512, +}; + +use crate::internal::{utils, WasmTestBuilder, DEFAULT_WASM_COSTS}; + +/// This function allows executing the contract stored in the given `wasm_file`, while capturing the +/// output. It is essentially the same functionality as `Executor::exec`, but the return value of +/// the contract is returned along with the effects. The purpose of this function is to test +/// installer contracts used in the new genesis process. +#[allow(clippy::too_many_arguments)] +pub fn exec( + config: EngineConfig, + builder: &mut WasmTestBuilder, + address: AccountHash, + wasm_file: &str, + block_time: u64, + deploy_hash: [u8; 32], + entry_point_name: &str, + args: RuntimeArgs, + extra_urefs: Vec, +) -> Option<(T, Vec, ExecutionEffect)> +where + S: StateProvider, + S::Error: Into, + EngineState: ExecutionEngineService, + T: FromBytes + CLTyped, +{ + let prestate = builder + .get_post_state_hash() + .as_slice() + .try_into() + .expect("should be able to make Blake2bHash from post-state hash"); + let tracking_copy = Rc::new(RefCell::new( + builder + .get_engine_state() + .tracking_copy(prestate) + .unwrap() + .expect("should be able to checkout tracking copy"), + )); + + let phase = Phase::Session; + let address_generator = { + let address_generator = AddressGenerator::new(&deploy_hash, phase); + Rc::new(RefCell::new(address_generator)) + }; + let gas_counter = Gas::default(); + let fn_store_id = { + let fn_store_id = AddressGenerator::new(&deploy_hash, phase); + Rc::new(RefCell::new(fn_store_id)) + }; + let gas_limit = Gas::new(U512::from(std::u64::MAX)); + let protocol_version = ProtocolVersion::V1_0_0; + let correlation_id = CorrelationId::new(); + let base_key = Key::Account(address); + + let account = builder.get_account(address).expect("should find account"); + + let mut named_keys = account.named_keys().clone(); + + let access_rights = { + let mut ret = runtime::extract_access_rights_from_keys(named_keys.values().cloned()); + let extras = runtime::extract_access_rights_from_urefs(extra_urefs.into_iter()); + ret.extend(extras.into_iter()); + ret + }; + + let protocol_data = { + let mint = builder.get_mint_contract_hash(); + let pos = builder.get_mint_contract_hash(); + let standard_payment = builder.get_standard_payment_contract_hash(); + ProtocolData::new(*DEFAULT_WASM_COSTS, mint, pos, standard_payment) + }; + + let context = RuntimeContext::new( + Rc::clone(&tracking_copy), + EntryPointType::Session, // Is it always? + &mut named_keys, + access_rights, + args, + BTreeSet::new(), + &account, + base_key, + BlockTime::new(block_time), + deploy_hash, + gas_limit, + gas_counter, + fn_store_id, + address_generator, + protocol_version, + correlation_id, + phase, + protocol_data, + ); + + let wasm_bytes = utils::read_wasm_file_bytes(wasm_file); + let deploy_item = ExecutableDeployItem::ModuleBytes { + module_bytes: wasm_bytes, + args: Vec::new(), + }; + + let wasm_costs = *DEFAULT_WASM_COSTS; + + let preprocessor = Preprocessor::new(wasm_costs); + let parity_module = builder + .get_engine_state() + .get_module( + tracking_copy, + &deploy_item, + &account, + correlation_id, + &preprocessor, + &protocol_version, + ) + .expect("should get wasm module"); + + let (instance, memory) = + runtime::instance_and_memory(parity_module.clone().take_module(), protocol_version) + .expect("should be able to make wasm instance from module"); + + let mut runtime = Runtime::new( + config, + Default::default(), + memory, + parity_module.take_module(), + context, + ); + + match instance.invoke_export(entry_point_name, &[], &mut runtime) { + Ok(_) => None, + Err(e) => { + if let Some(host_error) = e.as_host_error() { + // `ret` Trap is a success; downcast and attempt to extract result + let downcasted_error = host_error.downcast_ref::().unwrap(); + match downcasted_error { + execution::Error::Ret(ref ret_urefs) => { + let effect = runtime.context().effect(); + let urefs = ret_urefs.clone(); + + let value: T = runtime + .take_host_buffer() + .expect("should have return value in the host_buffer") + .into_t() + .expect("should deserialize return value"); + + Some((value, urefs, effect)) + } + + _ => None, + } + } else { + None + } + } + } +} diff --git a/grpc/test-support/src/internal/execute_request_builder.rs b/grpc/test-support/src/internal/execute_request_builder.rs new file mode 100644 index 0000000000..dbfd0003e1 --- /dev/null +++ b/grpc/test-support/src/internal/execute_request_builder.rs @@ -0,0 +1,124 @@ +use std::convert::TryInto; + +use rand::Rng; + +use node::contract_core::engine_state::{deploy_item::DeployItem, execute_request::ExecuteRequest}; +use types::{ + account::AccountHash, contracts::ContractVersion, runtime_args, ContractHash, ProtocolVersion, + RuntimeArgs, +}; + +use crate::internal::{DeployItemBuilder, DEFAULT_BLOCK_TIME, DEFAULT_PAYMENT}; + +const ARG_AMOUNT: &str = "amount"; + +#[derive(Debug)] +pub struct ExecuteRequestBuilder { + execute_request: ExecuteRequest, +} + +impl ExecuteRequestBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn from_deploy_item(deploy_item: DeployItem) -> Self { + ExecuteRequestBuilder::new().push_deploy(deploy_item) + } + + pub fn push_deploy(mut self, deploy: DeployItem) -> Self { + self.execute_request.deploys.push(Ok(deploy)); + self + } + + pub fn with_pre_state_hash(mut self, pre_state_hash: &[u8]) -> Self { + self.execute_request.parent_state_hash = pre_state_hash.try_into().unwrap(); + self + } + + pub fn with_block_time(mut self, block_time: u64) -> Self { + self.execute_request.block_time = block_time; + self + } + + pub fn with_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self { + self.execute_request.protocol_version = protocol_version; + self + } + + pub fn build(self) -> ExecuteRequest { + self.execute_request + } + + pub fn standard( + account_hash: AccountHash, + session_file: &str, + session_args: RuntimeArgs, + ) -> Self { + let mut rng = rand::thread_rng(); + let deploy_hash: [u8; 32] = rng.gen(); + + let deploy = DeployItemBuilder::new() + .with_address(account_hash) + .with_session_code(session_file, session_args) + .with_empty_payment_bytes(runtime_args! { + ARG_AMOUNT => *DEFAULT_PAYMENT + }) + .with_authorization_keys(&[account_hash]) + .with_deploy_hash(deploy_hash) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy) + } + + pub fn contract_call_by_hash( + sender: AccountHash, + contract_hash: ContractHash, + entry_point: &str, + args: RuntimeArgs, + ) -> Self { + let mut rng = rand::thread_rng(); + let deploy_hash: [u8; 32] = rng.gen(); + + let deploy = DeployItemBuilder::new() + .with_address(sender) + .with_stored_session_hash(contract_hash, entry_point, args) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[sender]) + .with_deploy_hash(deploy_hash) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy) + } + + /// Calls a versioned contract from contract package hash key_name + pub fn versioned_contract_call_by_hash_key_name( + sender: AccountHash, + hash_key_name: &str, + version: Option, + entry_point_name: &str, + args: RuntimeArgs, + ) -> Self { + let mut rng = rand::thread_rng(); + let deploy_hash: [u8; 32] = rng.gen(); + + let deploy = DeployItemBuilder::new() + .with_address(sender) + .with_stored_versioned_contract_by_name(hash_key_name, version, entry_point_name, args) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[sender]) + .with_deploy_hash(deploy_hash) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy) + } +} + +impl Default for ExecuteRequestBuilder { + fn default() -> Self { + let mut execute_request: ExecuteRequest = Default::default(); + execute_request.block_time = DEFAULT_BLOCK_TIME; + execute_request.protocol_version = ProtocolVersion::V1_0_0; + ExecuteRequestBuilder { execute_request } + } +} diff --git a/grpc/test-support/src/internal/mod.rs b/grpc/test-support/src/internal/mod.rs new file mode 100644 index 0000000000..fc92d6cd5c --- /dev/null +++ b/grpc/test-support/src/internal/mod.rs @@ -0,0 +1,89 @@ +mod additive_map_diff; +mod deploy_item_builder; +pub mod exec_with_return; +mod execute_request_builder; +mod upgrade_request_builder; +pub mod utils; +mod wasm_test_builder; + +use lazy_static::lazy_static; +use num_traits::identities::Zero; + +use node::contract_core::engine_state::{ + genesis::{ExecConfig, GenesisAccount, GenesisConfig}, + run_genesis_request::RunGenesisRequest, +}; +use node::contract_shared::wasm_costs::WasmCosts; +use node::contract_shared::{motes::Motes, newtypes::Blake2bHash, test_utils}; +use types::{account::AccountHash, ProtocolVersion, U512}; + +use super::{DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE}; +pub use additive_map_diff::AdditiveMapDiff; +pub use deploy_item_builder::DeployItemBuilder; +pub use execute_request_builder::ExecuteRequestBuilder; +pub use upgrade_request_builder::UpgradeRequestBuilder; +pub use wasm_test_builder::{ + InMemoryWasmTestBuilder, LmdbWasmTestBuilder, WasmTestBuilder, WasmTestResult, +}; + +pub const MINT_INSTALL_CONTRACT: &str = "mint_install.wasm"; +pub const POS_INSTALL_CONTRACT: &str = "pos_install.wasm"; +pub const STANDARD_PAYMENT_INSTALL_CONTRACT: &str = "standard_payment_install.wasm"; + +pub const DEFAULT_CHAIN_NAME: &str = "gerald"; +pub const DEFAULT_GENESIS_TIMESTAMP: u64 = 0; +pub const DEFAULT_BLOCK_TIME: u64 = 0; +pub const MOCKED_ACCOUNT_ADDRESS: AccountHash = AccountHash::new([48u8; 32]); + +pub const DEFAULT_ACCOUNT_KEY: AccountHash = DEFAULT_ACCOUNT_ADDR; + +pub const ARG_AMOUNT: &str = "amount"; + +lazy_static! { + pub static ref DEFAULT_GENESIS_CONFIG_HASH: Blake2bHash = [42; 32].into(); + pub static ref DEFAULT_ACCOUNTS: Vec = { + let mut ret = Vec::new(); + let genesis_account = GenesisAccount::new( + DEFAULT_ACCOUNT_ADDR, + Motes::new(DEFAULT_ACCOUNT_INITIAL_BALANCE.into()), + Motes::zero(), + ); + ret.push(genesis_account); + ret + }; + pub static ref DEFAULT_PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::V1_0_0; + pub static ref DEFAULT_PAYMENT: U512 = 100_000_000.into(); + pub static ref DEFAULT_WASM_COSTS: WasmCosts = test_utils::wasm_costs_mock(); + pub static ref DEFAULT_EXEC_CONFIG: ExecConfig = { + let mint_installer_bytes; + let pos_installer_bytes; + let standard_payment_installer_bytes; + mint_installer_bytes = utils::read_wasm_file_bytes(MINT_INSTALL_CONTRACT); + pos_installer_bytes = utils::read_wasm_file_bytes(POS_INSTALL_CONTRACT); + standard_payment_installer_bytes = + utils::read_wasm_file_bytes(STANDARD_PAYMENT_INSTALL_CONTRACT); + + ExecConfig::new( + mint_installer_bytes, + pos_installer_bytes, + standard_payment_installer_bytes, + DEFAULT_ACCOUNTS.clone(), + *DEFAULT_WASM_COSTS, + ) + }; + pub static ref DEFAULT_GENESIS_CONFIG: GenesisConfig = { + GenesisConfig::new( + DEFAULT_CHAIN_NAME.to_string(), + DEFAULT_GENESIS_TIMESTAMP, + *DEFAULT_PROTOCOL_VERSION, + DEFAULT_EXEC_CONFIG.clone(), + ) + }; + pub static ref DEFAULT_RUN_GENESIS_REQUEST: RunGenesisRequest = { + RunGenesisRequest::new( + *DEFAULT_GENESIS_CONFIG_HASH, + *DEFAULT_PROTOCOL_VERSION, + DEFAULT_EXEC_CONFIG.clone(), + ) + }; +} diff --git a/grpc/test-support/src/internal/upgrade_request_builder.rs b/grpc/test-support/src/internal/upgrade_request_builder.rs new file mode 100644 index 0000000000..7fab2ea12d --- /dev/null +++ b/grpc/test-support/src/internal/upgrade_request_builder.rs @@ -0,0 +1,103 @@ +use engine_grpc_server::engine_server::{ + ipc::{ + ChainSpec_ActivationPoint, ChainSpec_CostTable_WasmCosts, ChainSpec_UpgradePoint, + DeployCode, UpgradeRequest, + }, + state, +}; +use node::contract_shared::wasm_costs::WasmCosts; +use types::ProtocolVersion; + +pub struct UpgradeRequestBuilder { + pre_state_hash: Vec, + current_protocol_version: state::ProtocolVersion, + new_protocol_version: state::ProtocolVersion, + upgrade_installer: DeployCode, + new_costs: Option, + activation_point: ChainSpec_ActivationPoint, +} + +impl UpgradeRequestBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn with_pre_state_hash(mut self, pre_state_hash: &[u8]) -> Self { + self.pre_state_hash = pre_state_hash.to_vec(); + self + } + + pub fn with_current_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self { + self.current_protocol_version = protocol_version.into(); + self + } + + pub fn with_new_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self { + self.new_protocol_version = protocol_version.into(); + self + } + + pub fn with_installer_code(mut self, upgrade_installer: DeployCode) -> Self { + self.upgrade_installer = upgrade_installer; + self + } + + pub fn with_new_costs(mut self, wasm_costs: WasmCosts) -> Self { + let mut new_costs = ChainSpec_CostTable_WasmCosts::new(); + new_costs.set_regular(wasm_costs.regular); + new_costs.set_opcodes_mul(wasm_costs.opcodes_mul); + new_costs.set_opcodes_div(wasm_costs.opcodes_div); + new_costs.set_mul(wasm_costs.mul); + new_costs.set_div(wasm_costs.div); + new_costs.set_grow_mem(wasm_costs.grow_mem); + new_costs.set_initial_mem(wasm_costs.initial_mem); + new_costs.set_max_stack_height(wasm_costs.max_stack_height); + new_costs.set_mem(wasm_costs.mem); + new_costs.set_memcpy(wasm_costs.memcpy); + self.new_costs = Some(new_costs); + self + } + + pub fn with_activation_point(mut self, rank: u64) -> Self { + self.activation_point = { + let mut ret = ChainSpec_ActivationPoint::new(); + ret.set_rank(rank); + ret + }; + self + } + + pub fn build(self) -> UpgradeRequest { + let mut upgrade_point = ChainSpec_UpgradePoint::new(); + upgrade_point.set_activation_point(self.activation_point); + match self.new_costs { + None => {} + Some(new_costs) => { + let mut cost_table = + engine_grpc_server::engine_server::ipc::ChainSpec_CostTable::new(); + cost_table.set_wasm(new_costs); + upgrade_point.set_new_costs(cost_table); + } + } + upgrade_point.set_protocol_version(self.new_protocol_version); + upgrade_point.set_upgrade_installer(self.upgrade_installer); + + let mut upgrade_request = UpgradeRequest::new(); + upgrade_request.set_protocol_version(self.current_protocol_version); + upgrade_request.set_upgrade_point(upgrade_point); + upgrade_request + } +} + +impl Default for UpgradeRequestBuilder { + fn default() -> Self { + UpgradeRequestBuilder { + pre_state_hash: Default::default(), + current_protocol_version: Default::default(), + new_protocol_version: Default::default(), + upgrade_installer: Default::default(), + new_costs: None, + activation_point: Default::default(), + } + } +} diff --git a/grpc/test-support/src/internal/utils.rs b/grpc/test-support/src/internal/utils.rs new file mode 100644 index 0000000000..59b7cbbf0c --- /dev/null +++ b/grpc/test-support/src/internal/utils.rs @@ -0,0 +1,199 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, + rc::Rc, +}; + +use lazy_static::lazy_static; + +use node::contract_core::engine_state::{ + execution_result::ExecutionResult, + genesis::{ExecConfig, GenesisAccount, GenesisConfig}, + run_genesis_request::RunGenesisRequest, + Error, +}; +use node::contract_shared::{ + account::Account, additive_map::AdditiveMap, gas::Gas, stored_value::StoredValue, + transform::Transform, +}; +use types::Key; + +use crate::internal::{ + DEFAULT_CHAIN_NAME, DEFAULT_GENESIS_CONFIG_HASH, DEFAULT_GENESIS_TIMESTAMP, + DEFAULT_PROTOCOL_VERSION, DEFAULT_WASM_COSTS, MINT_INSTALL_CONTRACT, POS_INSTALL_CONTRACT, + STANDARD_PAYMENT_INSTALL_CONTRACT, +}; + +lazy_static! { + static ref RUST_WORKSPACE_PATH: PathBuf = { + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent().and_then(Path::parent) + .expect("CARGO_MANIFEST_DIR should have parent"); + assert!(path.exists(), "Workspace path {} does not exists", path.display()); + path.to_path_buf() + }; + // The location of compiled Wasm files if compiled from the Rust sources within the CasperLabs + // repo, i.e. 'CasperLabs/execution-engine/target/wasm32-unknown-unknown/release/'. + static ref RUST_WORKSPACE_WASM_PATH: PathBuf = { + let path = RUST_WORKSPACE_PATH + .join("target") + .join("wasm32-unknown-unknown") + .join("release"); + assert!(path.exists(), "Rust WASM path {} does not exists", path.display()); + path + }; + // The location of compiled Wasm files if running from within the 'tests' crate generated by the + // cargo-casperlabs tool, i.e. 'wasm/'. + static ref RUST_TOOL_WASM_PATH: PathBuf = env::current_dir() + .expect("should get current working dir") + .join("wasm"); + // The location of compiled Wasm files if compiled from the Rust sources within the CasperLabs + // repo where `CARGO_TARGET_DIR` is set, i.e. + // '/wasm32-unknown-unknown/release/'. + static ref MAYBE_CARGO_TARGET_DIR_WASM_PATH: Option = { + let maybe_target = std::env::var("CARGO_TARGET_DIR").ok(); + maybe_target.as_ref().map(|path| { + Path::new(path) + .join("wasm32-unknown-unknown") + .join("release") + }) + }; + // The location of compiled Wasm files if compiled from the Rust sources within the CasperLabs + // repo, i.e. 'CasperLabs/execution-engine/target/wasm32-unknown-unknown/release/'. + static ref ASSEMBLY_SCRIPT_WORKSPACE_WASM_PATH: PathBuf = { + let path = RUST_WORKSPACE_PATH + .join("target-as"); + + assert!(path.exists(), "AssemblyScript WASM path {} does not exist.", path.display()); + path + }; + static ref WASM_PATHS: Vec = get_compiled_wasm_paths(); +} + +/// Constructs a list of paths that should be considered while looking for a compiled wasm file. +fn get_compiled_wasm_paths() -> Vec { + let mut ret = vec![ + // Contracts compiled with typescript are tried first + #[cfg(feature = "use-as-wasm")] + ASSEMBLY_SCRIPT_WORKSPACE_WASM_PATH.clone(), + RUST_WORKSPACE_WASM_PATH.clone(), + RUST_TOOL_WASM_PATH.clone(), + ]; + if let Some(cargo_target_dir_wasm_path) = &*MAYBE_CARGO_TARGET_DIR_WASM_PATH { + ret.push(cargo_target_dir_wasm_path.clone()); + }; + ret +} + +/// Reads a given compiled contract file based on path +pub fn read_wasm_file_bytes>(contract_file: T) -> Vec { + let mut attempted_paths = vec![]; + + if contract_file.as_ref().is_relative() { + // Find first path to a given file found in a list of paths + for wasm_path in WASM_PATHS.iter() { + let mut filename = wasm_path.clone(); + filename.push(contract_file.as_ref()); + if let Ok(wasm_bytes) = fs::read(&filename) { + return wasm_bytes; + } + attempted_paths.push(filename); + } + } + // Try just opening in case the arg is a valid path relative to current working dir, or is a + // valid absolute path. + if let Ok(wasm_bytes) = fs::read(contract_file.as_ref()) { + return wasm_bytes; + } + attempted_paths.push(contract_file.as_ref().to_owned()); + + let mut error_msg = + "\nFailed to open compiled Wasm file. Tried the following locations:\n".to_string(); + for attempted_path in attempted_paths { + error_msg = format!("{} - {}\n", error_msg, attempted_path.display()); + } + + panic!("{}\n", error_msg); +} + +pub fn create_exec_config(accounts: Vec) -> ExecConfig { + let mint_installer_bytes = read_wasm_file_bytes(MINT_INSTALL_CONTRACT); + let proof_of_stake_installer_bytes = read_wasm_file_bytes(POS_INSTALL_CONTRACT); + let standard_payment_installer_bytes = read_wasm_file_bytes(STANDARD_PAYMENT_INSTALL_CONTRACT); + let wasm_costs = *DEFAULT_WASM_COSTS; + ExecConfig::new( + mint_installer_bytes, + proof_of_stake_installer_bytes, + standard_payment_installer_bytes, + accounts, + wasm_costs, + ) +} + +pub fn create_genesis_config(accounts: Vec) -> GenesisConfig { + let name = DEFAULT_CHAIN_NAME.to_string(); + let timestamp = DEFAULT_GENESIS_TIMESTAMP; + let protocol_version = *DEFAULT_PROTOCOL_VERSION; + let exec_config = create_exec_config(accounts); + + GenesisConfig::new(name, timestamp, protocol_version, exec_config) +} + +pub fn create_run_genesis_request(accounts: Vec) -> RunGenesisRequest { + let exec_config = create_exec_config(accounts); + RunGenesisRequest::new( + *DEFAULT_GENESIS_CONFIG_HASH, + *DEFAULT_PROTOCOL_VERSION, + exec_config, + ) +} + +pub fn get_exec_costs, I: IntoIterator>( + exec_response: I, +) -> Vec { + exec_response + .into_iter() + .map(|res| res.as_ref().cost()) + .collect() +} + +pub fn get_success_result(response: &[Rc]) -> &ExecutionResult { + &*response.get(0).expect("should have a result") +} + +pub fn get_precondition_failure(response: &[Rc]) -> &Error { + let result = response.get(0).expect("should have a result"); + assert!( + result.has_precondition_failure(), + "should be a precondition failure" + ); + result.as_error().expect("should have an error") +} + +pub fn get_error_message, I: IntoIterator>( + execution_result: I, +) -> String { + let errors = execution_result + .into_iter() + .enumerate() + .filter_map(|(i, result)| { + if let ExecutionResult::Failure { error, .. } = result.as_ref() { + Some(format!("{}: {:?}", i, error)) + } else { + None + } + }) + .collect::>(); + errors.join("\n") +} + +#[allow(clippy::implicit_hasher)] +pub fn get_account(transforms: &AdditiveMap, account: &Key) -> Option { + transforms.get(account).and_then(|transform| { + if let Transform::Write(StoredValue::Account(account)) = transform { + Some(account.to_owned()) + } else { + None + } + }) +} diff --git a/grpc/test-support/src/internal/wasm_test_builder.rs b/grpc/test-support/src/internal/wasm_test_builder.rs new file mode 100644 index 0000000000..8ce43a9f27 --- /dev/null +++ b/grpc/test-support/src/internal/wasm_test_builder.rs @@ -0,0 +1,719 @@ +use std::{ + collections::HashMap, + convert::{TryFrom, TryInto}, + ffi::OsStr, + fs, + path::PathBuf, + rc::Rc, + sync::Arc, +}; + +use grpc::RequestOptions; +use lmdb::DatabaseFlags; +use log::LevelFilter; + +use engine_grpc_server::engine_server::{ + ipc::{ + CommitRequest, CommitResponse, GenesisResponse, QueryRequest, UpgradeRequest, + UpgradeResponse, + }, + ipc_grpc::ExecutionEngineService, + mappings::{MappingError, TransformMap}, + transforms::TransformEntry, +}; +use node::contract_core::{ + engine_state::{ + execute_request::ExecuteRequest, execution_result::ExecutionResult, + run_genesis_request::RunGenesisRequest, EngineConfig, EngineState, SYSTEM_ACCOUNT_ADDR, + }, + execution, +}; +use node::contract_shared::{ + account::Account, + additive_map::AdditiveMap, + gas::Gas, + logging::{self, Settings, Style}, + newtypes::{Blake2bHash, CorrelationId}, + page_size, + stored_value::StoredValue, + transform::Transform, +}; +use node::contract_storage::{ + global_state::{in_memory::InMemoryGlobalState, lmdb::LmdbGlobalState, StateProvider}, + protocol_data_store::lmdb::LmdbProtocolDataStore, + transaction_source::lmdb::LmdbEnvironment, + trie_store::lmdb::LmdbTrieStore, +}; +use types::{ + account::AccountHash, + bytesrepr::{self}, + CLValue, Contract, ContractHash, ContractWasm, Key, URef, U512, +}; + +use crate::internal::utils; + +/// LMDB initial map size is calculated based on DEFAULT_LMDB_PAGES and systems page size. +/// +/// This default value should give 50MiB initial map size by default. +const DEFAULT_LMDB_PAGES: usize = 128_000; + +/// This is appended to the data dir path provided to the `LmdbWasmTestBuilder` in order to match +/// the behavior of `get_data_dir()` in "engine-grpc-server/src/main.rs". +const GLOBAL_STATE_DIR: &str = "global_state"; + +pub type InMemoryWasmTestBuilder = WasmTestBuilder; +pub type LmdbWasmTestBuilder = WasmTestBuilder; + +/// Builder for simple WASM test +pub struct WasmTestBuilder { + /// [`EngineState`] is wrapped in [`Rc`] to work around a missing [`Clone`] implementation + engine_state: Rc>, + /// [`ExecutionResult`] is wrapped in [`Rc`] to work around a missing [`Clone`] implementation + exec_responses: Vec>>, + upgrade_responses: Vec, + genesis_hash: Option>, + post_state_hash: Option>, + /// Cached transform maps after subsequent successful runs i.e. `transforms[0]` is for first + /// exec call etc. + transforms: Vec>, + bonded_validators: Vec>, + /// Cached genesis transforms + genesis_account: Option, + /// Genesis transforms + genesis_transforms: Option>, + /// Mint contract key + mint_contract_hash: Option, + /// PoS contract key + pos_contract_hash: Option, + /// Standard payment contract key + standard_payment_hash: Option, +} + +impl WasmTestBuilder { + fn initialize_logging() { + let log_settings = Settings::new(LevelFilter::Error).with_style(Style::HumanReadable); + let _ = logging::initialize(log_settings); + } +} + +impl Default for InMemoryWasmTestBuilder { + fn default() -> Self { + Self::initialize_logging(); + let engine_config = EngineConfig::new() + .with_use_system_contracts(cfg!(feature = "use-system-contracts")) + .with_enable_bonding(cfg!(feature = "enable-bonding")); + + let global_state = InMemoryGlobalState::empty().expect("should create global state"); + let engine_state = EngineState::new(global_state, engine_config); + + WasmTestBuilder { + engine_state: Rc::new(engine_state), + exec_responses: Vec::new(), + upgrade_responses: Vec::new(), + genesis_hash: None, + post_state_hash: None, + transforms: Vec::new(), + bonded_validators: Vec::new(), + genesis_account: None, + genesis_transforms: None, + mint_contract_hash: None, + pos_contract_hash: None, + standard_payment_hash: None, + } + } +} + +// TODO: Deriving `Clone` for `WasmTestBuilder` doesn't work correctly (unsure why), so +// implemented by hand here. Try to derive in the future with a different compiler version. +impl Clone for WasmTestBuilder { + fn clone(&self) -> Self { + WasmTestBuilder { + engine_state: Rc::clone(&self.engine_state), + exec_responses: self.exec_responses.clone(), + upgrade_responses: self.upgrade_responses.clone(), + genesis_hash: self.genesis_hash.clone(), + post_state_hash: self.post_state_hash.clone(), + transforms: self.transforms.clone(), + bonded_validators: self.bonded_validators.clone(), + genesis_account: self.genesis_account.clone(), + genesis_transforms: self.genesis_transforms.clone(), + mint_contract_hash: self.mint_contract_hash, + pos_contract_hash: self.pos_contract_hash, + standard_payment_hash: self.standard_payment_hash, + } + } +} + +/// A wrapper type to disambiguate builder from an actual result +#[derive(Clone)] +pub struct WasmTestResult(WasmTestBuilder); + +impl WasmTestResult { + /// Access the builder + pub fn builder(&self) -> &WasmTestBuilder { + &self.0 + } +} + +impl InMemoryWasmTestBuilder { + pub fn new( + global_state: InMemoryGlobalState, + engine_config: EngineConfig, + post_state_hash: Vec, + ) -> Self { + Self::initialize_logging(); + let engine_state = EngineState::new(global_state, engine_config); + WasmTestBuilder { + engine_state: Rc::new(engine_state), + genesis_hash: Some(post_state_hash.clone()), + post_state_hash: Some(post_state_hash), + ..Default::default() + } + } +} + +impl LmdbWasmTestBuilder { + pub fn new_with_config + ?Sized>( + data_dir: &T, + engine_config: EngineConfig, + ) -> Self { + Self::initialize_logging(); + let page_size = page_size::get_page_size().expect("should get page size"); + let global_state_dir = Self::create_and_get_global_state_dir(data_dir); + let environment = Arc::new( + LmdbEnvironment::new(&global_state_dir, page_size * DEFAULT_LMDB_PAGES) + .expect("should create LmdbEnvironment"), + ); + let trie_store = Arc::new( + LmdbTrieStore::new(&environment, None, DatabaseFlags::empty()) + .expect("should create LmdbTrieStore"), + ); + let protocol_data_store = Arc::new( + LmdbProtocolDataStore::new(&environment, None, DatabaseFlags::empty()) + .expect("should create LmdbProtocolDataStore"), + ); + let global_state = LmdbGlobalState::empty(environment, trie_store, protocol_data_store) + .expect("should create LmdbGlobalState"); + let engine_state = EngineState::new(global_state, engine_config); + WasmTestBuilder { + engine_state: Rc::new(engine_state), + exec_responses: Vec::new(), + upgrade_responses: Vec::new(), + genesis_hash: None, + post_state_hash: None, + transforms: Vec::new(), + bonded_validators: Vec::new(), + genesis_account: None, + genesis_transforms: None, + mint_contract_hash: None, + pos_contract_hash: None, + standard_payment_hash: None, + } + } + + pub fn new + ?Sized>(data_dir: &T) -> Self { + Self::new_with_config(data_dir, Default::default()) + } + + /// Creates new instance of builder and applies values only which allows the engine state to be + /// swapped with a new one, possibly after running genesis once and reusing existing database + /// (i.e. LMDB). + pub fn new_with_config_and_result + ?Sized>( + data_dir: &T, + engine_config: EngineConfig, + result: &WasmTestResult, + ) -> Self { + let mut builder = Self::new_with_config(data_dir, engine_config); + // Applies existing properties from gi + builder.genesis_hash = result.0.genesis_hash.clone(); + builder.post_state_hash = result.0.post_state_hash.clone(); + builder.bonded_validators = result.0.bonded_validators.clone(); + builder.mint_contract_hash = result.0.mint_contract_hash; + builder.pos_contract_hash = result.0.pos_contract_hash; + builder + } + + /// Creates a new instance of builder using the supplied configurations, opening wrapped LMDBs + /// (e.g. in the Trie and Data stores) rather than creating them. + pub fn open + ?Sized>( + data_dir: &T, + engine_config: EngineConfig, + post_state_hash: Vec, + ) -> Self { + Self::initialize_logging(); + let page_size = page_size::get_page_size().expect("should get page size"); + let global_state_dir = Self::create_and_get_global_state_dir(data_dir); + let environment = Arc::new( + LmdbEnvironment::new(&global_state_dir, page_size * DEFAULT_LMDB_PAGES) + .expect("should create LmdbEnvironment"), + ); + let trie_store = + Arc::new(LmdbTrieStore::open(&environment, None).expect("should open LmdbTrieStore")); + let protocol_data_store = Arc::new( + LmdbProtocolDataStore::open(&environment, None) + .expect("should open LmdbProtocolDataStore"), + ); + let global_state = LmdbGlobalState::empty(environment, trie_store, protocol_data_store) + .expect("should create LmdbGlobalState"); + let engine_state = EngineState::new(global_state, engine_config); + WasmTestBuilder { + engine_state: Rc::new(engine_state), + exec_responses: Vec::new(), + upgrade_responses: Vec::new(), + genesis_hash: None, + post_state_hash: Some(post_state_hash), + transforms: Vec::new(), + bonded_validators: Vec::new(), + genesis_account: None, + genesis_transforms: None, + mint_contract_hash: None, + pos_contract_hash: None, + standard_payment_hash: None, + } + } + + fn create_and_get_global_state_dir + ?Sized>(data_dir: &T) -> PathBuf { + let global_state_path = { + let mut path = PathBuf::from(data_dir); + path.push(GLOBAL_STATE_DIR); + path + }; + fs::create_dir_all(&global_state_path) + .unwrap_or_else(|_| panic!("Expected to create {}", global_state_path.display())); + global_state_path + } +} + +impl WasmTestBuilder +where + S: StateProvider, + S::Error: Into, + EngineState: ExecutionEngineService, +{ + /// Carries on attributes from TestResult for further executions + pub fn from_result(result: WasmTestResult) -> Self { + WasmTestBuilder { + engine_state: result.0.engine_state, + exec_responses: Vec::new(), + upgrade_responses: Vec::new(), + genesis_hash: result.0.genesis_hash, + post_state_hash: result.0.post_state_hash, + transforms: Vec::new(), + bonded_validators: result.0.bonded_validators, + genesis_account: result.0.genesis_account, + mint_contract_hash: result.0.mint_contract_hash, + pos_contract_hash: result.0.pos_contract_hash, + standard_payment_hash: result.0.standard_payment_hash, + genesis_transforms: result.0.genesis_transforms, + } + } + + pub fn run_genesis(&mut self, run_genesis_request: &RunGenesisRequest) -> &mut Self { + let system_account = Key::Account(SYSTEM_ACCOUNT_ADDR); + let run_genesis_request_proto = run_genesis_request + .to_owned() + .try_into() + .expect("could not parse"); + + let genesis_response = self + .engine_state + .run_genesis(RequestOptions::new(), run_genesis_request_proto) + .wait_drop_metadata() + .expect("Unable to get genesis response"); + if genesis_response.has_failed_deploy() { + panic!( + "genesis failure: {:?}", + genesis_response.get_failed_deploy().to_owned() + ); + } + + let state_root_hash: Blake2bHash = genesis_response + .get_success() + .get_poststate_hash() + .try_into() + .expect("Unable to get root hash"); + + let transforms = get_genesis_transforms(&genesis_response); + + let genesis_account = + utils::get_account(&transforms, &system_account).expect("Unable to get system account"); + + let maybe_protocol_data = self + .engine_state + .get_protocol_data(run_genesis_request.protocol_version()) + .expect("should read protocol data"); + let protocol_data = maybe_protocol_data.expect("should have protocol data stored"); + + self.genesis_hash = Some(state_root_hash.to_vec()); + self.post_state_hash = Some(state_root_hash.to_vec()); + self.mint_contract_hash = Some(protocol_data.mint()); + self.pos_contract_hash = Some(protocol_data.proof_of_stake()); + self.standard_payment_hash = Some(protocol_data.standard_payment()); + self.genesis_account = Some(genesis_account); + self.genesis_transforms = Some(transforms); + self + } + + pub fn query( + &self, + maybe_post_state: Option>, + base_key: Key, + path: &[&str], + ) -> Result { + let post_state = maybe_post_state + .or_else(|| self.post_state_hash.clone()) + .expect("builder must have a post-state hash"); + + let path_vec: Vec = path.iter().map(|s| String::from(*s)).collect(); + + let query_request = create_query_request(post_state, base_key, path_vec); + + let mut query_response = self + .engine_state + .query(RequestOptions::new(), query_request) + .wait_drop_metadata() + .expect("should get query response"); + + if query_response.has_failure() { + return Err(query_response.take_failure()); + } + bytesrepr::deserialize(query_response.take_success()).map_err(|err| format!("{}", err)) + } + + pub fn exec(&mut self, mut exec_request: ExecuteRequest) -> &mut Self { + let exec_request = { + let hash = self + .post_state_hash + .clone() + .expect("expected post_state_hash"); + exec_request.parent_state_hash = + hash.as_slice().try_into().expect("expected a valid hash"); + exec_request + }; + let exec_response = self + .engine_state + .run_execute(CorrelationId::new(), exec_request); + assert!(exec_response.is_ok()); + // Parse deploy results + let execution_results = exec_response.as_ref().unwrap(); + // Cache transformations + self.transforms.extend( + execution_results + .iter() + .map(|res| res.effect().transforms.clone()), + ); + self.exec_responses + .push(exec_response.unwrap().into_iter().map(Rc::new).collect()); + self + } + + /// Commit effects of previous exec call on the latest post-state hash. + pub fn commit(&mut self) -> &mut Self { + let prestate_hash = self + .post_state_hash + .clone() + .expect("Should have genesis hash"); + + let effects = self.transforms.last().cloned().unwrap_or_default(); + + self.commit_effects(prestate_hash, effects) + } + + /// Sends raw commit request to the current engine response. + /// + /// Can be used where result is not necessary + pub fn commit_transforms( + &self, + prestate_hash: Vec, + effects: AdditiveMap, + ) -> CommitResponse { + let commit_request = create_commit_request(&prestate_hash, &effects); + + self.engine_state + .commit(RequestOptions::new(), commit_request) + .wait_drop_metadata() + .expect("Should have commit response") + } + + /// Runs a commit request, expects a successful response, and + /// overwrites existing cached post state hash with a new one. + pub fn commit_effects( + &mut self, + prestate_hash: Vec, + effects: AdditiveMap, + ) -> &mut Self { + let mut commit_response = self.commit_transforms(prestate_hash, effects); + if !commit_response.has_success() { + panic!( + "Expected commit success but received a failure instead: {:?}", + commit_response + ); + } + let mut commit_success = commit_response.take_success(); + self.post_state_hash = Some(commit_success.take_poststate_hash().to_vec()); + let bonded_validators = commit_success + .take_bonded_validators() + .into_iter() + .map(TryInto::try_into) + .collect::, MappingError>>() + .unwrap(); + self.bonded_validators.push(bonded_validators); + self + } + + pub fn upgrade_with_upgrade_request( + &mut self, + upgrade_request: &mut UpgradeRequest, + ) -> &mut Self { + let upgrade_request = { + let hash = self + .post_state_hash + .clone() + .expect("expected post_state_hash"); + upgrade_request.set_parent_state_hash(hash.to_vec()); + upgrade_request + }; + let upgrade_response = self + .engine_state + .upgrade(RequestOptions::new(), upgrade_request.clone()) + .wait_drop_metadata() + .expect("should upgrade"); + + let upgrade_success = upgrade_response.get_success(); + self.post_state_hash = Some(upgrade_success.get_post_state_hash().to_vec()); + + self.upgrade_responses.push(upgrade_response.clone()); + self + } + + /// Expects a successful run and caches transformations + pub fn expect_success(&mut self) -> &mut Self { + // Check first result, as only first result is interesting for a simple test + let exec_response = self + .exec_responses + .last() + .expect("Expected to be called after run()"); + let exec_result = exec_response + .get(0) + .expect("Unable to get first deploy result"); + + if exec_result.is_failure() { + panic!( + "Expected successful execution result, but instead got: {:?}", + exec_response, + ); + } + self + } + + pub fn is_error(&self) -> bool { + let exec_response = self + .exec_responses + .last() + .expect("Expected to be called after run()"); + let exec_result = exec_response + .get(0) + .expect("Unable to get first execution result"); + exec_result.is_failure() + } + + /// Gets the transform map that's cached between runs + pub fn get_transforms(&self) -> Vec> { + self.transforms.clone() + } + + pub fn get_bonded_validators(&self) -> Vec> { + self.bonded_validators.clone() + } + + /// Gets genesis account (if present) + pub fn get_genesis_account(&self) -> &Account { + self.genesis_account + .as_ref() + .expect("Unable to obtain genesis account. Please run genesis first.") + } + + pub fn get_mint_contract_hash(&self) -> ContractHash { + self.mint_contract_hash + .expect("Unable to obtain mint contract. Please run genesis first.") + } + + pub fn get_pos_contract_hash(&self) -> ContractHash { + self.pos_contract_hash + .expect("Unable to obtain pos contract. Please run genesis first.") + } + + pub fn get_standard_payment_contract_hash(&self) -> ContractHash { + self.standard_payment_hash + .expect("Unable to obtain standard payment contract. Please run genesis first.") + } + + pub fn get_genesis_transforms(&self) -> &AdditiveMap { + &self + .genesis_transforms + .as_ref() + .expect("should have genesis transforms") + } + + pub fn get_genesis_hash(&self) -> Vec { + self.genesis_hash + .clone() + .expect("Genesis hash should be present. Should be called after run_genesis.") + } + + pub fn get_post_state_hash(&self) -> Vec { + self.post_state_hash + .clone() + .expect("Should have post-state hash.") + } + + pub fn get_engine_state(&self) -> &EngineState { + &self.engine_state + } + + pub fn get_exec_responses(&self) -> &Vec>> { + &self.exec_responses + } + + pub fn get_exec_response(&self, index: usize) -> Option<&Vec>> { + self.exec_responses.get(index) + } + + pub fn get_exec_responses_count(&self) -> usize { + self.exec_responses.len() + } + + pub fn get_upgrade_response(&self, index: usize) -> Option<&UpgradeResponse> { + self.upgrade_responses.get(index) + } + + pub fn finish(&self) -> WasmTestResult { + WasmTestResult(self.clone()) + } + + pub fn get_pos_contract(&self) -> Contract { + let pos_contract: Key = self + .pos_contract_hash + .expect("should have pos contract uref") + .into(); + self.query(None, pos_contract, &[]) + .and_then(|v| v.try_into().map_err(|error| format!("{:?}", error))) + .expect("should find PoS URef") + } + + pub fn get_purse_balance(&self, purse: URef) -> U512 { + let purse_addr = purse.addr(); + let balance_mapping_key = Key::Hash(purse_addr); + + let base_key = self + .query(None, balance_mapping_key, &[]) + .and_then(|v| CLValue::try_from(v).map_err(|error| format!("{:?}", error))) + .and_then(|cl_value| cl_value.into_t().map_err(|error| format!("{:?}", error))) + .expect("should find balance uref"); + + self.query(None, base_key, &[]) + .and_then(|v| CLValue::try_from(v).map_err(|error| format!("{:?}", error))) + .and_then(|cl_value| cl_value.into_t().map_err(|error| format!("{:?}", error))) + .expect("should parse balance into a U512") + } + + pub fn get_account(&self, account_hash: AccountHash) -> Option { + match self.query(None, Key::Account(account_hash), &[]) { + Ok(account_value) => match account_value { + StoredValue::Account(account) => Some(account), + _ => None, + }, + Err(_) => None, + } + } + + pub fn get_contract(&self, contract_hash: ContractHash) -> Option { + let contract_value: StoredValue = self + .query(None, contract_hash.into(), &[]) + .expect("should have contract value"); + + if let StoredValue::Contract(contract) = contract_value { + Some(contract) + } else { + None + } + } + + pub fn get_contract_wasm(&self, contract_hash: ContractHash) -> Option { + let contract_value: StoredValue = self + .query(None, contract_hash.into(), &[]) + .expect("should have contract value"); + + if let StoredValue::ContractWasm(contract_wasm) = contract_value { + Some(contract_wasm) + } else { + None + } + } + + pub fn exec_costs(&self, index: usize) -> Vec { + let exec_response = self + .get_exec_response(index) + .expect("should have exec response"); + utils::get_exec_costs(exec_response) + } + + pub fn last_exec_gas_cost(&self) -> Gas { + let exec_response = self + .exec_responses + .last() + .expect("Expected to be called after run()"); + let exec_result = exec_response.get(0).expect("should have result"); + exec_result.cost() + } + + pub fn exec_error_message(&self, index: usize) -> Option { + let response = self.get_exec_response(index)?; + Some(utils::get_error_message(response)) + } + + pub fn exec_commit_finish(&mut self, execute_request: ExecuteRequest) -> WasmTestResult { + self.exec(execute_request) + .expect_success() + .commit() + .finish() + } +} + +fn create_query_request(post_state: Vec, base_key: Key, path: Vec) -> QueryRequest { + let mut query_request = QueryRequest::new(); + + query_request.set_state_hash(post_state); + query_request.set_base_key(base_key.into()); + query_request.set_path(path.into()); + + query_request +} + +#[allow(clippy::implicit_hasher)] +fn create_commit_request( + prestate_hash: &[u8], + effects: &AdditiveMap, +) -> CommitRequest { + let effects: Vec = effects + .iter() + .map(|(k, t)| (k.to_owned(), t.to_owned()).into()) + .collect(); + + let mut commit_request = CommitRequest::new(); + commit_request.set_prestate_hash(prestate_hash.to_vec()); + commit_request.set_effects(effects.into()); + commit_request +} + +#[allow(clippy::implicit_hasher)] +fn get_genesis_transforms(genesis_response: &GenesisResponse) -> AdditiveMap { + let commit_transforms: TransformMap = genesis_response + .get_success() + .get_effect() + .get_transform_map() + .to_vec() + .try_into() + .expect("should convert"); + commit_transforms.into_inner() +} diff --git a/grpc/test-support/src/lib.rs b/grpc/test-support/src/lib.rs new file mode 100644 index 0000000000..34130139fa --- /dev/null +++ b/grpc/test-support/src/lib.rs @@ -0,0 +1,94 @@ +//! A library to support testing of Wasm smart contracts for use on the CasperLabs Platform. +//! +//! # Example +//! Consider a contract held in "contract.wasm" which stores an arbitrary `String` under a `Key` +//! named "special_value": +//! ```no_run +//! # use contract as casperlabs_contract; +//! # use types as casperlabs_types; +//! use casperlabs_contract::contract_api::{runtime, storage}; +//! use casperlabs_types::Key; +//! const KEY: &str = "special_value"; +//! const ARG_VALUE: &str = "value"; +//! +//! #[no_mangle] +//! pub extern "C" fn call() { +//! let value: String = runtime::get_named_arg(ARG_VALUE); +//! let value_ref = storage::new_uref(value); +//! let value_key: Key = value_ref.into(); +//! runtime::put_key(KEY, value_key); +//! } +//! ``` +//! +//! The test could be written as follows: +//! ```no_run +//! # use types as casperlabs_types; +//! use casperlabs_engine_test_support::{Code, Error, SessionBuilder, TestContextBuilder, Value}; +//! use casperlabs_types::{account::AccountHash, U512, RuntimeArgs, runtime_args}; +//! +//! const MY_ACCOUNT: AccountHash = AccountHash::new([7u8; 32]); +//! const KEY: &str = "special_value"; +//! const VALUE: &str = "hello world"; +//! const ARG_MESSAGE: &str = "message"; +//! +//! let mut context = TestContextBuilder::new() +//! .with_account(MY_ACCOUNT, U512::from(128_000_000)) +//! .build(); +//! +//! // The test framework checks for compiled Wasm files in '/wasm'. Paths +//! // relative to the current working dir (e.g. 'wasm/contract.wasm') can also be used, as can +//! // absolute paths. +//! let session_code = Code::from("contract.wasm"); +//! let session_args = runtime_args! { +//! ARG_MESSAGE => VALUE, +//! }; +//! let session = SessionBuilder::new(session_code, session_args) +//! .with_address(MY_ACCOUNT) +//! .with_authorization_keys(&[MY_ACCOUNT]) +//! .build(); +//! +//! let result_of_query: Result = context.run(session).query(MY_ACCOUNT, &[KEY]); +//! +//! let returned_value = result_of_query.expect("should be a value"); +//! +//! let expected_value = Value::from_t(VALUE.to_string()).expect("should construct Value"); +//! assert_eq!(expected_value, returned_value); +//! ``` + +#![doc(html_root_url = "https://docs.rs/casperlabs-engine-test-support/0.8.0")] +#![doc( + html_favicon_url = "https://raw.githubusercontent.com/CasperLabs/CasperLabs/dev/images/CasperLabs_Logo_Favicon_RGB_50px.png", + html_logo_url = "https://raw.githubusercontent.com/CasperLabs/CasperLabs/dev/images/CasperLabs_Logo_Symbol_RGB.png", + test(attr(forbid(warnings))) +)] +#![warn(missing_docs)] + +mod account; +mod code; +mod error; +// This module is not intended to be used by third party crates. +#[doc(hidden)] +pub mod internal; +mod session; +mod test_context; +mod value; + +pub use account::Account; +pub use code::Code; +pub use error::{Error, Result}; +pub use session::{Session, SessionBuilder, SessionTransferInfo}; +pub use test_context::{TestContext, TestContextBuilder}; +pub use types::account::AccountHash; +pub use value::Value; + +/// The address of a [`URef`](types::URef) (unforgeable reference) on the network. +pub type URefAddr = [u8; 32]; + +/// The hash of a smart contract stored on the network, which can be used to reference the contract. +pub type Hash = [u8; 32]; + +/// Default test account address. +pub const DEFAULT_ACCOUNT_ADDR: AccountHash = AccountHash::new([6u8; 32]); + +/// Default initial balance of a test account in motes. +pub const DEFAULT_ACCOUNT_INITIAL_BALANCE: u64 = 100_000_000_000; diff --git a/grpc/test-support/src/session.rs b/grpc/test-support/src/session.rs new file mode 100644 index 0000000000..468e96075b --- /dev/null +++ b/grpc/test-support/src/session.rs @@ -0,0 +1,166 @@ +use rand::Rng; + +use node::contract_core::engine_state::execute_request::ExecuteRequest; +use types::{runtime_args, ProtocolVersion, RuntimeArgs, URef, U512}; + +use crate::{ + internal::{DeployItemBuilder, ExecuteRequestBuilder, DEFAULT_PAYMENT}, + AccountHash, Code, +}; + +const ARG_AMOUNT: &str = "amount"; + +/// Transfer Information for validating a transfer including gas usage from source +pub struct SessionTransferInfo { + pub(crate) source_purse: URef, + pub(crate) maybe_target_purse: Option, + pub(crate) transfer_amount: U512, +} + +impl SessionTransferInfo { + /// Constructs a new `SessionTransferInfo` containing information for validating a transfer + /// when `test_context.run()` occurs. + /// + /// Assertion will be made that `source_purse` is debited `transfer_amount` with gas costs + /// handled. If given, assertion will be made that `maybe_target_purse` is credited + /// `transfer_amount` + pub fn new( + source_purse: URef, + maybe_target_purse: Option, + transfer_amount: U512, + ) -> Self { + SessionTransferInfo { + source_purse, + maybe_target_purse, + transfer_amount, + } + } +} + +/// A single session, i.e. a single request to execute a single deploy within the test context. +pub struct Session { + pub(crate) inner: ExecuteRequest, + pub(crate) expect_success: bool, + pub(crate) check_transfer_success: Option, + pub(crate) commit: bool, +} + +/// Builder for a [`Session`]. +pub struct SessionBuilder { + er_builder: ExecuteRequestBuilder, + di_builder: DeployItemBuilder, + expect_failure: bool, + check_transfer_success: Option, + without_commit: bool, +} + +impl SessionBuilder { + /// Constructs a new `SessionBuilder` containing a deploy with the provided session code and + /// session args, and with default values for the account address, payment code args, gas price, + /// authorization keys and protocol version. + pub fn new(session_code: Code, session_args: RuntimeArgs) -> Self { + let di_builder = DeployItemBuilder::new() + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }); + let di_builder = match session_code { + Code::Path(path) => di_builder.with_session_code(path, session_args), + Code::NamedKey(name, entry_point) => { + di_builder.with_stored_session_named_key(&name, &entry_point, session_args) + } + Code::Hash(hash, entry_point) => { + di_builder.with_stored_session_hash(hash, &entry_point, session_args) + } + }; + let expect_failure = false; + let check_transfer_success = None; + let without_commit = false; + Self { + er_builder: Default::default(), + di_builder, + expect_failure, + check_transfer_success, + without_commit, + } + } + + /// Returns `self` with the provided account address set. + pub fn with_address(mut self, address: AccountHash) -> Self { + self.di_builder = self.di_builder.with_address(address); + self + } + + /// Returns `self` with the provided payment code and args set. + pub fn with_payment_code(mut self, code: Code, args: RuntimeArgs) -> Self { + self.di_builder = match code { + Code::Path(path) => self.di_builder.with_payment_code(path, args), + Code::NamedKey(name, entry_point) => { + self.di_builder + .with_stored_payment_named_key(&name, &entry_point, args) + } + Code::Hash(hash, entry_point) => { + self.di_builder + .with_stored_payment_hash(hash, &entry_point, args) + } + }; + self + } + + /// Returns `self` with the provided block time set. + pub fn with_block_time(mut self, block_time: u64) -> Self { + self.er_builder = self.er_builder.with_block_time(block_time); + self + } + + /// Returns `self` with the provided gas price set. + pub fn with_gas_price(mut self, price: u64) -> Self { + self.di_builder = self.di_builder.with_gas_price(price); + self + } + + /// Returns `self` with the provided authorization keys set. + pub fn with_authorization_keys(mut self, keys: &[AccountHash]) -> Self { + self.di_builder = self.di_builder.with_authorization_keys(keys); + self + } + + /// Returns `self` with the provided protocol version set. + pub fn with_protocol_version(mut self, version: ProtocolVersion) -> Self { + self.er_builder = self.er_builder.with_protocol_version(version); + self + } + + /// Will disable the expect_success call during Text_Context.run() method when expected to fail. + pub fn without_expect_success(mut self) -> Self { + self.expect_failure = true; + self + } + + /// Provide SessionTransferInfo to validate transfer including gas used from source account + pub fn with_check_transfer_success( + mut self, + session_transfer_info: SessionTransferInfo, + ) -> Self { + self.check_transfer_success = Some(session_transfer_info); + self + } + + /// Do not perform commit within the ['TestContext'].['run'] method. + pub fn without_commit(mut self) -> Self { + self.without_commit = true; + self + } + + /// Builds the [`Session`]. + pub fn build(self) -> Session { + let mut rng = rand::thread_rng(); + let execute_request = self + .er_builder + .push_deploy(self.di_builder.with_deploy_hash(rng.gen()).build()) + .build(); + Session { + inner: execute_request, + expect_success: !self.expect_failure, + check_transfer_success: self.check_transfer_success, + commit: !self.without_commit, + } + } +} diff --git a/grpc/test-support/src/test_context.rs b/grpc/test-support/src/test_context.rs new file mode 100644 index 0000000000..3609e9f44b --- /dev/null +++ b/grpc/test-support/src/test_context.rs @@ -0,0 +1,190 @@ +use num_traits::identities::Zero; + +use node::contract_core::engine_state::{ + genesis::{GenesisAccount, GenesisConfig}, + run_genesis_request::RunGenesisRequest, + CONV_RATE, +}; + +use node::contract_shared::motes::Motes; +use types::{AccessRights, Key, URef, U512}; + +use crate::{ + internal::{InMemoryWasmTestBuilder, DEFAULT_GENESIS_CONFIG, DEFAULT_GENESIS_CONFIG_HASH}, + Account, AccountHash, Error, Result, Session, URefAddr, Value, +}; + +/// Context in which to run a test of a Wasm smart contract. +pub struct TestContext { + inner: InMemoryWasmTestBuilder, +} + +impl TestContext { + fn maybe_purse_balance(&self, purse_uref: Option) -> Option { + match purse_uref { + None => None, + Some(purse_uref) => { + let purse_balance = self.get_balance(purse_uref.addr()); + Some(Motes::new(purse_balance)) + } + } + } + + /// Runs the supplied [`Session`] checking specified expectations of the execution and + /// subsequent commit of transforms are met. + /// + /// If `session` was built without + /// [`without_expect_success()`](crate::SessionBuilder::without_expect_success) (the default) + /// then `run()` will panic if execution of the deploy fails. + /// + /// If `session` was built with + /// [`with_check_transfer_success()`](crate::SessionBuilder::with_check_transfer_success), (not + /// the default) then `run()` will verify transfer balances including gas used. + /// + /// If `session` was built without + /// [`without_commit()`](crate::SessionBuilder::without_commit) (the default), then `run()` will + /// commit the resulting transforms. + pub fn run(&mut self, session: Session) -> &mut Self { + match session.check_transfer_success { + Some(session_transfer_info) => { + let source_initial_balance = self + .maybe_purse_balance(Some(session_transfer_info.source_purse)) + .expect("source purse balance"); + let maybe_target_initial_balance = + self.maybe_purse_balance(session_transfer_info.maybe_target_purse); + + let builder = self.inner.exec(session.inner); + if session.expect_success { + builder.expect_success(); + } + if session.commit { + builder.commit(); + } + + let gas_cost = builder.last_exec_gas_cost(); + match maybe_target_initial_balance { + None => (), + Some(target_initial_balance) => { + let target_ending_balance = self + .maybe_purse_balance(session_transfer_info.maybe_target_purse) + .expect("target ending balance"); + let expected_target_ending_balance = target_initial_balance + + Motes::new(session_transfer_info.transfer_amount); + if expected_target_ending_balance != target_ending_balance { + panic!( + "target ending balance does not match; expected: {} actual: {}", + expected_target_ending_balance, target_ending_balance + ); + } + } + } + + let expected_source_ending_balance = source_initial_balance + - Motes::new(session_transfer_info.transfer_amount) + - Motes::from_gas(gas_cost, CONV_RATE).expect("motes from gas"); + let actual_source_ending_balance = self + .maybe_purse_balance(Some(session_transfer_info.source_purse)) + .expect("source ending balance"); + if expected_source_ending_balance != actual_source_ending_balance { + panic!( + "source ending balance does not match; expected: {} actual: {}", + expected_source_ending_balance, actual_source_ending_balance + ); + } + } + None => { + let builder = self.inner.exec(session.inner); + if session.expect_success { + builder.expect_success(); + } + if session.commit { + builder.commit(); + } + } + } + self + } + + /// Queries for a [`Value`] stored under the given `key` and `path`. + /// + /// Returns an [`Error`] if not found. + pub fn query>(&self, key: AccountHash, path: &[T]) -> Result { + let path = path.iter().map(AsRef::as_ref).collect::>(); + self.inner + .query(None, Key::Account(key), &path) + .map(Value::new) + .map_err(Error::from) + } + + /// Gets the balance of the purse under the given [`URefAddr`]. + /// + /// Note that this requires performing an earlier query to retrieve `purse_addr`. + pub fn get_balance(&self, purse_addr: URefAddr) -> U512 { + let purse = URef::new(purse_addr, AccessRights::READ); + self.inner.get_purse_balance(purse) + } + + /// Gets the main purse [`URef`] from an [`Account`] stored under a [`PublicKey`], or `None`. + pub fn main_purse_address(&self, account_key: AccountHash) -> Option { + match self.inner.get_account(account_key) { + Some(account) => Some(account.main_purse()), + None => None, + } + } + + // TODO: Remove this once test can use query + /// Gets an [`Account`] stored under a [`PublicKey`], or `None`. + pub fn get_account(&self, account_key: AccountHash) -> Option { + match self.inner.get_account(account_key) { + Some(account) => Some(account.into()), + None => None, + } + } +} + +/// Builder for a [`TestContext`]. +pub struct TestContextBuilder { + genesis_config: GenesisConfig, +} + +impl TestContextBuilder { + /// Constructs a new `TestContextBuilder` initialised with default values for an account, i.e. + /// an account at [`DEFAULT_ACCOUNT_ADDR`](crate::DEFAULT_ACCOUNT_ADDR) with an initial balance + /// of [`DEFAULT_ACCOUNT_INITIAL_BALANCE`](crate::DEFAULT_ACCOUNT_INITIAL_BALANCE) which will be + /// added to the Genesis block. + pub fn new() -> Self { + TestContextBuilder { + genesis_config: DEFAULT_GENESIS_CONFIG.clone(), + } + } + + /// Returns `self` with the provided account's details added to existing ones, for inclusion in + /// the Genesis block. + /// + /// Note: `initial_balance` represents the number of motes. + pub fn with_account(mut self, address: AccountHash, initial_balance: U512) -> Self { + let new_account = GenesisAccount::new(address, Motes::new(initial_balance), Motes::zero()); + self.genesis_config + .ee_config_mut() + .push_account(new_account); + self + } + + /// Builds the [`TestContext`]. + pub fn build(self) -> TestContext { + let mut inner = InMemoryWasmTestBuilder::default(); + let run_genesis_request = RunGenesisRequest::new( + *DEFAULT_GENESIS_CONFIG_HASH, + self.genesis_config.protocol_version(), + self.genesis_config.take_ee_config(), + ); + inner.run_genesis(&run_genesis_request); + TestContext { inner } + } +} + +impl Default for TestContextBuilder { + fn default() -> Self { + TestContextBuilder::new() + } +} diff --git a/grpc/test-support/src/value.rs b/grpc/test-support/src/value.rs new file mode 100644 index 0000000000..877b936f89 --- /dev/null +++ b/grpc/test-support/src/value.rs @@ -0,0 +1,41 @@ +use std::convert::{TryFrom, TryInto}; + +use node::contract_shared::stored_value::StoredValue; +use types::{ + bytesrepr::{FromBytes, ToBytes}, + CLTyped, CLValue, +}; + +use crate::{Account, Result}; + +/// A value stored under a given key on the network. +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct Value { + inner: StoredValue, +} + +impl Value { + pub(crate) fn new(stored_value: StoredValue) -> Self { + Value { + inner: stored_value, + } + } + + /// Constructs a `Value` from `t`. + pub fn from_t(t: T) -> Result { + let cl_value = CLValue::from_t(t)?; + let inner = StoredValue::CLValue(cl_value); + Ok(Value { inner }) + } + + /// Consumes and converts `self` back into its underlying type. + pub fn into_t(self) -> Result { + let cl_value = CLValue::try_from(self.inner)?; + Ok(cl_value.into_t()?) + } + + /// Consumes and converts `self` into an `Account` or errors. + pub fn into_account(self) -> Result { + self.inner.try_into() + } +} diff --git a/grpc/test-support/tests/version_numbers.rs b/grpc/test-support/tests/version_numbers.rs new file mode 100644 index 0000000000..9f1d04aae3 --- /dev/null +++ b/grpc/test-support/tests/version_numbers.rs @@ -0,0 +1,4 @@ +#[test] +fn test_html_root_url() { + version_sync::assert_html_root_url_updated!("src/lib.rs"); +} diff --git a/grpc/tests/Cargo.toml b/grpc/tests/Cargo.toml new file mode 100644 index 0000000000..db30ab0c14 --- /dev/null +++ b/grpc/tests/Cargo.toml @@ -0,0 +1,77 @@ +[package] +name = "casperlabs-engine-tests" +version = "0.1.0" +authors = ["Ed Hastings , Henry Till "] +edition = "2018" + +[dependencies] +base16 = "0.2.1" +clap = "2" +contract = { path = "../../smart-contracts/contract", package = "casperlabs-contract" } +crossbeam-channel = "0.4.0" +node = { path = "../../node", package = "casperlabs-node" } +engine-grpc-server = { path = "../server", package = "casperlabs-engine-grpc-server" } +engine-test-support = { path = "../test-support", package = "casperlabs-engine-test-support" } +env_logger = "0.7.1" +grpc = "0.6.1" +log = "0.4.8" +rand = "0.7.3" +serde_json = "1" +types = { path = "../../types", package = "casperlabs-types", features = ["std"] } + +[dev-dependencies] +criterion = "0.3.0" +lazy_static = "1" +num-traits = "0.2.10" +serde_json = "1" +tempfile = "3" +wabt = "0.9.2" +assert_matches = "1.3.0" + +[features] +default = ["contract/std", "contract/test-support", "node/test-support", "engine-test-support/test-support"] +enable-bonding = ["engine-test-support/enable-bonding"] +use-as-wasm = ["engine-test-support/use-as-wasm"] +use-system-contracts = ["engine-test-support/use-system-contracts"] +no-unstable-features = [ + "contract/no-unstable-features", + "node/no-unstable-features", + "engine-grpc-server/no-unstable-features", + "engine-test-support/no-unstable-features", + "types/no-unstable-features" +] + +[lib] +bench = false + +[[bench]] +name = "transfer_bench" +harness = false + +[[bin]] +name = "state-initializer" +path = "src/profiling/state_initializer.rs" +test = false +bench = false + +[[bin]] +name = "simple-transfer" +path = "src/profiling/simple_transfer.rs" +test = false +bench = false + +[[bin]] +name = "concurrent-executor" +path = "src/profiling/concurrent_executor.rs" +test = false +bench = false + +[[bin]] +name = "host-function-metrics" +path = "src/profiling/host_function_metrics.rs" +test = false +bench = false + +[[test]] +name = "metrics" +path = "src/logging/metrics.rs" diff --git a/grpc/tests/benches/transfer_bench.rs b/grpc/tests/benches/transfer_bench.rs new file mode 100644 index 0000000000..97f33786c5 --- /dev/null +++ b/grpc/tests/benches/transfer_bench.rs @@ -0,0 +1,325 @@ +use std::{path::Path, time::Duration}; + +use criterion::{ + criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion, Throughput, +}; +use tempfile::TempDir; + +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::EngineConfig; +use types::{account::AccountHash, runtime_args, Key, RuntimeArgs, URef, U512}; + +const CONTRACT_CREATE_ACCOUNTS: &str = "create_accounts.wasm"; +const CONTRACT_CREATE_PURSES: &str = "create_purses.wasm"; +const CONTRACT_TRANSFER_TO_EXISTING_ACCOUNT: &str = "transfer_to_existing_account.wasm"; +const CONTRACT_TRANSFER_TO_PURSE: &str = "transfer_to_purse.wasm"; + +/// Size of batch used in multiple execs benchmark, and multiple deploys per exec cases. +const TRANSFER_BATCH_SIZE: u64 = 3; +const PER_RUN_FUNDING: u64 = 10_000_000; +const TARGET_ADDR: AccountHash = AccountHash::new([127; 32]); +const ARG_AMOUNT: &str = "amount"; +const ARG_ACCOUNTS: &str = "accounts"; +const ARG_SEED_AMOUNT: &str = "seed_amount"; +const ARG_TOTAL_PURSES: &str = "total_purses"; +const ARG_TARGET: &str = "target"; +const ARG_TARGET_PURSE: &str = "target_purse"; + +/// Converts an integer into an array of type [u8; 32] by converting integer +/// into its big endian representation and embedding it at the end of the +/// range. +fn make_deploy_hash(i: u64) -> [u8; 32] { + let mut result = [128; 32]; + result[32 - 8..].copy_from_slice(&i.to_be_bytes()); + result +} + +fn bootstrap(data_dir: &Path, accounts: Vec, amount: U512) -> LmdbWasmTestBuilder { + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_CREATE_ACCOUNTS, + runtime_args! { ARG_ACCOUNTS => accounts, ARG_SEED_AMOUNT => amount }, + ) + .build(); + + let engine_config = EngineConfig::new() + .with_use_system_contracts(cfg!(feature = "use-system-contracts")) + .with_enable_bonding(cfg!(feature = "enable-bonding")); + + let mut builder = LmdbWasmTestBuilder::new_with_config(data_dir, engine_config); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .expect_success() + .commit(); + + builder +} + +fn create_purses( + builder: &mut LmdbWasmTestBuilder, + source: AccountHash, + total_purses: u64, + purse_amount: U512, +) -> Vec { + let exec_request = ExecuteRequestBuilder::standard( + source, + CONTRACT_CREATE_PURSES, + runtime_args! { ARG_TOTAL_PURSES => total_purses, ARG_SEED_AMOUNT => purse_amount }, + ) + .build(); + + builder.exec(exec_request).expect_success().commit(); + + // Return creates purses for given account by filtering named keys + let query_result = builder + .query(None, Key::Account(source), &[]) + .expect("should query target"); + let account = query_result + .as_account() + .unwrap_or_else(|| panic!("result should be account but received {:?}", query_result)); + + (0..total_purses) + .map(|index| { + let purse_lookup_key = format!("purse:{}", index); + let purse_uref = account + .named_keys() + .get(&purse_lookup_key) + .and_then(Key::as_uref) + .unwrap_or_else(|| panic!("should get named key {} as uref", purse_lookup_key)); + *purse_uref + }) + .collect() +} + +/// Uses multiple exec requests with a single deploy to transfer tokens. Executes all transfers in +/// batch determined by value of TRANSFER_BATCH_SIZE. +fn transfer_to_account_multiple_execs( + builder: &mut LmdbWasmTestBuilder, + account: AccountHash, + should_commit: bool, +) { + let amount = U512::one(); + + for _ in 0..TRANSFER_BATCH_SIZE { + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_EXISTING_ACCOUNT, + runtime_args! { + ARG_TARGET => account, + ARG_AMOUNT => amount, + }, + ) + .build(); + + let builder = builder.exec(exec_request).expect_success(); + if should_commit { + builder.commit(); + } + } +} + +/// Executes multiple deploys per single exec with based on TRANSFER_BATCH_SIZE. +fn transfer_to_account_multiple_deploys( + builder: &mut LmdbWasmTestBuilder, + account: AccountHash, + should_commit: bool, +) { + let mut exec_builder = ExecuteRequestBuilder::new(); + + for i in 0..TRANSFER_BATCH_SIZE { + let deploy = DeployItemBuilder::default() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => U512::from(PER_RUN_FUNDING) }) + .with_session_code( + CONTRACT_TRANSFER_TO_EXISTING_ACCOUNT, + runtime_args! { + ARG_TARGET => account, + ARG_AMOUNT => U512::one(), + }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash(make_deploy_hash(i)) // deploy_hash + .build(); + exec_builder = exec_builder.push_deploy(deploy); + } + + let exec_request = exec_builder.build(); + + let builder = builder.exec(exec_request).expect_success(); + if should_commit { + builder.commit(); + } +} + +/// Uses multiple exec requests with a single deploy to transfer tokens from purse to purse. +/// Executes all transfers in batch determined by value of TRANSFER_BATCH_SIZE. +fn transfer_to_purse_multiple_execs( + builder: &mut LmdbWasmTestBuilder, + purse: URef, + should_commit: bool, +) { + let amount = U512::one(); + + for _ in 0..TRANSFER_BATCH_SIZE { + let exec_request = ExecuteRequestBuilder::standard( + TARGET_ADDR, + CONTRACT_TRANSFER_TO_PURSE, + runtime_args! { ARG_TARGET_PURSE => purse, ARG_AMOUNT => amount }, + ) + .build(); + + let builder = builder.exec(exec_request).expect_success(); + if should_commit { + builder.commit(); + } + } +} + +/// Executes multiple deploys per single exec with based on TRANSFER_BATCH_SIZE. +fn transfer_to_purse_multiple_deploys( + builder: &mut LmdbWasmTestBuilder, + purse: URef, + should_commit: bool, +) { + let mut exec_builder = ExecuteRequestBuilder::new(); + + for i in 0..TRANSFER_BATCH_SIZE { + let deploy = DeployItemBuilder::default() + .with_address(TARGET_ADDR) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_session_code( + CONTRACT_TRANSFER_TO_PURSE, + runtime_args! { ARG_TARGET_PURSE => purse, ARG_AMOUNT => U512::one() }, + ) + .with_authorization_keys(&[TARGET_ADDR]) + .with_deploy_hash(make_deploy_hash(i)) // deploy_hash + .build(); + exec_builder = exec_builder.push_deploy(deploy); + } + + let exec_request = exec_builder.build(); + + let builder = builder.exec(exec_request).expect_success(); + if should_commit { + builder.commit(); + } +} + +pub fn transfer_to_existing_accounts(group: &mut BenchmarkGroup, should_commit: bool) { + let target_account = TARGET_ADDR; + let bootstrap_accounts = vec![target_account]; + + let data_dir = TempDir::new().expect("should create temp dir"); + let mut builder = bootstrap(data_dir.path(), bootstrap_accounts.clone(), U512::one()); + + group.bench_function( + format!( + "transfer_to_existing_account_multiple_execs/{}/{}", + TRANSFER_BATCH_SIZE, should_commit + ), + |b| { + b.iter(|| { + // Execute multiple deploys with multiple exec requests + transfer_to_account_multiple_execs(&mut builder, target_account, should_commit) + }) + }, + ); + + let data_dir = TempDir::new().expect("should create temp dir"); + let mut builder = bootstrap(data_dir.path(), bootstrap_accounts, U512::one()); + + group.bench_function( + format!( + "transfer_to_existing_account_multiple_deploys_per_exec/{}/{}", + TRANSFER_BATCH_SIZE, should_commit + ), + |b| { + b.iter(|| { + // Execute multiple deploys with a single exec request + transfer_to_account_multiple_deploys(&mut builder, target_account, should_commit) + }) + }, + ); +} + +pub fn transfer_to_existing_purses(group: &mut BenchmarkGroup, should_commit: bool) { + let target_account = TARGET_ADDR; + let bootstrap_accounts = vec![target_account]; + + let data_dir = TempDir::new().expect("should create temp dir"); + let mut builder = bootstrap( + data_dir.path(), + bootstrap_accounts.clone(), + *DEFAULT_PAYMENT * 100, + ); + let purses = create_purses(&mut builder, target_account, 1, U512::one()); + + group.bench_function( + format!( + "transfer_to_purse_multiple_execs/{}/{}", + TRANSFER_BATCH_SIZE, should_commit + ), + |b| { + let target_purse = purses[0]; + b.iter(|| { + // Execute multiple deploys with mutliple exec request + transfer_to_purse_multiple_execs(&mut builder, target_purse, should_commit) + }) + }, + ); + + let data_dir = TempDir::new().expect("should create temp dir"); + let mut builder = bootstrap(data_dir.path(), bootstrap_accounts, *DEFAULT_PAYMENT * 10); + let purses = create_purses(&mut builder, TARGET_ADDR, 1, U512::one()); + + group.bench_function( + format!( + "transfer_to_purse_multiple_deploys_per_exec/{}/{}", + TRANSFER_BATCH_SIZE, should_commit + ), + |b| { + let target_purse = purses[0]; + b.iter(|| { + // Execute multiple deploys with a single exec request + transfer_to_purse_multiple_deploys(&mut builder, target_purse, should_commit) + }) + }, + ); +} + +pub fn transfer_bench(c: &mut Criterion) { + let mut group = c.benchmark_group("tps"); + + // Minimum number of samples and measurement times to decrease the total time of this benchmark. + // This may or may not decrease the quality of the numbers. + group.sample_size(10); + group.measurement_time(Duration::from_secs(10)); + + // Measure by elements where one element per second is one transaction per second + group.throughput(Throughput::Elements(TRANSFER_BATCH_SIZE)); + + // Transfers to existing accounts, no commits + transfer_to_existing_accounts(&mut group, false); + + // Transfers to existing purses, no commits + transfer_to_existing_purses(&mut group, false); + + // Transfers to existing accounts, with commits + transfer_to_existing_accounts(&mut group, true); + + // Transfers to existing purses, with commits + transfer_to_existing_purses(&mut group, true); + + group.finish(); +} + +criterion_group!(benches, transfer_bench); +criterion_main!(benches); diff --git a/grpc/tests/src/lib.rs b/grpc/tests/src/lib.rs new file mode 100644 index 0000000000..090944ac86 --- /dev/null +++ b/grpc/tests/src/lib.rs @@ -0,0 +1,3 @@ +pub mod profiling; +#[cfg(test)] +mod test; diff --git a/grpc/tests/src/logging/metrics.rs b/grpc/tests/src/logging/metrics.rs new file mode 100644 index 0000000000..ca23dbd1e9 --- /dev/null +++ b/grpc/tests/src/logging/metrics.rs @@ -0,0 +1,95 @@ +use std::sync::{Arc, Mutex}; + +use log::{LevelFilter, Metadata, Record}; +use serde_json::Value; + +use engine_test_support::internal::{InMemoryWasmTestBuilder, MOCKED_ACCOUNT_ADDRESS}; +use node::contract_core::engine_state::EngineConfig; +use node::contract_shared::{ + logging::{self, Settings, TerminalLogger, PAYLOAD_KEY}, + newtypes::CorrelationId, + test_utils, +}; +use node::contract_storage::global_state::in_memory::InMemoryGlobalState; + +const PROPERTIES_KEY: &str = "properties"; +const CORRELATION_ID_KEY: &str = "correlation_id"; + +struct Logger { + terminal_logger: TerminalLogger, + log_lines: Arc>>, +} + +impl Logger { + fn new(buffer: Arc>>, settings: &Settings) -> Self { + Logger { + terminal_logger: TerminalLogger::new(settings), + log_lines: buffer, + } + } +} + +impl log::Log for Logger { + fn enabled(&self, metadata: &Metadata) -> bool { + self.terminal_logger.enabled(metadata) + } + + fn log(&self, record: &Record) { + if let Some(log_line) = self.terminal_logger.prepare_log_line(record) { + self.log_lines.lock().unwrap().push(log_line); + } + } + + fn flush(&self) {} +} + +fn extract_correlation_id_property(line: &str) -> Option { + if let Some(idx) = line.find(PAYLOAD_KEY) { + let start = idx + PAYLOAD_KEY.len(); + let end = line.len(); + let slice = &line[start..end]; + serde_json::from_str::(slice) + .ok() + .and_then(|full_value| full_value.get(PROPERTIES_KEY).cloned()) + .and_then(|properties_value| properties_value.get(CORRELATION_ID_KEY).cloned()) + .and_then(|correlation_id_value| correlation_id_value.as_str().map(String::from)) + } else { + None + } +} + +#[test] +fn should_commit_with_metrics() { + let settings = Settings::new(LevelFilter::Trace).with_metrics_enabled(true); + let log_lines = Arc::new(Mutex::new(vec![])); + let logger = Box::new(Logger::new(Arc::clone(&log_lines), &settings)); + let _ = logging::initialize_with_logger(logger, settings); + + let correlation_id = CorrelationId::new(); + let mocked_account = test_utils::mocked_account(MOCKED_ACCOUNT_ADDRESS); + let (global_state, root_hash) = + InMemoryGlobalState::from_pairs(correlation_id, &mocked_account).unwrap(); + + let engine_config = EngineConfig::new(); + + let result = + InMemoryWasmTestBuilder::new(global_state, engine_config, root_hash.to_vec()).finish(); + + let _commit_response = result + .builder() + .commit_transforms(root_hash.to_vec(), Default::default()); + + let mut log_lines = log_lines.lock().unwrap(); + + let expected_fragment = format!(r#""{}":"{}""#, CORRELATION_ID_KEY, correlation_id); + log_lines.retain(|line| line.contains(&expected_fragment)); + assert!( + !log_lines.is_empty(), + "at least one log line should contain the expected correlation ID" + ); + + for line in log_lines.iter() { + let extracted_correlation_id = extract_correlation_id_property(line).unwrap(); + assert_eq!(correlation_id.to_string(), extracted_correlation_id); + } +} diff --git a/grpc/tests/src/profiling/README.md b/grpc/tests/src/profiling/README.md new file mode 100644 index 0000000000..0d46e420a8 --- /dev/null +++ b/grpc/tests/src/profiling/README.md @@ -0,0 +1,141 @@ +# Overview + +This directory contains executable targets to allow for profiling code used to execute a transfer contract. + +# `state-initializer` + +This is used to initialize global state in preparation for running one of the other executables. It allows them to avoid taking into account the cost of installing the Proof of Stake and Mint contracts. + +It takes a single optional command line argument to specify the directory in which to store the persistent data and outputs the post-state hash from the commit response. This hash will be used as an input to other profiling executables. + +--- + +# `simple-transfer` + +This runs a single transfer via the `LmdbWasmTestBuilder` and is designed to be used along with `perf` to analyse the performance data. + +First, run `state-initializer` to set up a persistent global state, then the `simple-transfer` executable will make use of that state, and can be profiled. + +For more details on each, run the executable with `--help`. + +## Example usage + +To profile `simple-transfer` using `perf` and open the flamegraph in Firefox, follow these steps: + +* Install `perf` (see [this askubuntu answer](https://askubuntu.com/a/578618/75096)) +* Clone and add [Flamegraph](https://github.com/brendangregg/FlameGraph) to your path +* Run: + ```bash + cd CasperLabs/execution-engine/ + make build-contracts-rs + cd engine-tests/ + cargo build --release --bin state-initializer + cargo build --release --bin simple-transfer + ../target/release/state-initializer --data-dir=../target | perf record -g --call-graph dwarf ../target/release/simple-transfer --data-dir=../target + perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg + firefox flame.svg + ``` + + +## Troubleshooting + +Due to kernel hardening, `perf` may need some or all of the following changes to be made in order to run properly: + + +### Error message about `perf_event_paranoid`: + +See [this superuser answer](https://superuser.com/a/980757/463043) for details. In summary, to temporarily fix the issue: + +```bash +sudo sysctl -w kernel.perf_event_paranoid=-1 +``` + +and to permanently fix it: + +```bash +sudo sh -c 'echo kernel.perf_event_paranoid=-1 >> /etc/sysctl.d/99-my-settings-local.conf' +sysctl -p /etc/sysctl.conf +``` + + +### Error message about `kptr_restrict`: + +See [this S.O. answer](https://stackoverflow.com/a/36263349/2556117) for details. In summary, to temporarily fix the issue: + +```bash +sudo sysctl -w kernel.kptr_restrict=0 +``` + +and to permanently fix it: + +```bash +sudo sh -c 'echo kernel.kptr_restrict=0 >> /etc/sysctl.d/99-my-settings-local.conf' +sysctl -p /etc/sysctl.conf +``` + +--- + +# `concurrent-executor` + +This is a minimal client which repeatedly sends an `execute` request for a transfer to an instance of the `casperlabs-engine-grpc-server`. It runs a threadpool to parallelize sending the requests, and it's designed to allow testing the effects of varying the worker thread count in the server. + +## Example usage + +First build the contracts and run `state-initializer`: + +```bash +cd CasperLabs/execution-engine/ +make build-contracts-rs +cd engine-tests/ +HASH=$(cargo run --release --bin=state-initializer -- --data-dir=/tmp/CasperLabs/DataDir) +``` + +In a new terminal, run the server, using the same data directory populated by the `state-initializer`: + +```bash +cd CasperLabs/execution-engine/ +cargo run --release --bin=casperlabs-engine-grpc-server -- \ + /tmp/CasperLabs/Socket --data-dir=/tmp/CasperLabs/DataDir --threads=8 +``` + +**Note: to tell the server to use Wasm system contracts rather than host-side implementations, append ` -z` to the above command.** + +Then in the first terminal, run the client: + +```bash +RUST_LOG=concurrent_executor=info cargo run --release --bin=concurrent-executor -- \ + --socket=/tmp/CasperLabs/Socket --pre-state-hash=$HASH --threads=8 --requests=200 +``` + +**Note: by default, the wasmless transfer option is used. However the original wasm based transfer can be opted into by appending `-m=WASM` to the above command.** + +There is a bash script which automates this process, and which allows specifying the number of server threadpool threads, the number of client threadpool threads, the number of messages the client should send, and whether to use system contracts or not. + +```bash +cd CasperLabs/execution-engine/engine-tests/src/profiling/ +./concurrent_executor.sh 8 8 200 # without system contracts +./concurrent_executor.sh 8 8 200 -z # using system contracts +``` + +For logging, again set the `RUST_LOG` env var: + +```bash +RUST_LOG=concurrent_executor=info ./concurrent_executor.sh 8 8 200 +``` + +--- + +# `host-function-metrics` + +This tool generates CSV files containing metrics for the host functions callable by Wasm smart contracts and which are currently unmetered. + +Note that running the tool with the default 10,000 repetitions can take in excess of half an hour to complete. + +```bash +cd CasperLabs/execution-engine/ +make build-contracts-rs +cd engine-tests/ +cargo build --release --bin state-initializer +cargo build --release --bin host-function-metrics +../target/release/state-initializer --data-dir=../target | ../target/release/host-function-metrics --data-dir=../target --output-dir=../target/host-function-metrics +``` diff --git a/grpc/tests/src/profiling/concurrent_executor.rs b/grpc/tests/src/profiling/concurrent_executor.rs new file mode 100644 index 0000000000..b26f61993d --- /dev/null +++ b/grpc/tests/src/profiling/concurrent_executor.rs @@ -0,0 +1,419 @@ +//! This executable is designed to send a series of contracts in parallel to a running instance of +//! `casperlabs-engine-grpc-server` in order to gauge the server's performance. +//! +//! For details of how to run this executable, see the README in this directory or at +//! https://github.com/CasperLabs/CasperLabs/blob/dev/execution-engine/engine-tests/src/profiling/README.md#concurrent-executor + +use std::{ + iter::Sum, + sync::Arc, + thread::{self, JoinHandle}, + time::{Duration, Instant}, +}; + +use clap::{crate_version, App, Arg}; +use crossbeam_channel::{Iter, Receiver, Sender}; +use grpc::{ClientStubExt, RequestOptions}; +use log::info; + +use engine_grpc_server::engine_server::{ + ipc::ExecuteRequest, + ipc_grpc::{ExecutionEngineService, ExecutionEngineServiceClient}, +}; +use engine_test_support::internal::{DeployItemBuilder, ExecuteRequestBuilder, DEFAULT_PAYMENT}; +use types::{runtime_args, RuntimeArgs, U512}; + +use casperlabs_engine_tests::profiling; + +use crate::profiling::TransferMode; + +const APP_NAME: &str = "Concurrent Executor"; +const ABOUT: &str = + "A client which constructs several 'ExecuteRequest's and sends them concurrently to the \ + Execution Engine server.\n\nFirst run the 'state-initializer' executable to set up the \ + required global state, specifying a data dir. This outputs the pre-state hash required as an \ + input to 'concurrent-executor'. Then run the 'casperlabs-engine-grpc-server' specifying the \ + same data dir and a socket file. Finally, 'concurrent-executor' can be run using the same \ + socket file.\n\nTo enable logging, set the env var 'RUST_LOG=concurrent_executor=info'."; + +const SOCKET_ARG_NAME: &str = "socket"; +const SOCKET_ARG_SHORT: &str = "s"; +const SOCKET_ARG_VALUE_NAME: &str = "SOCKET"; +const SOCKET_ARG_HELP: &str = "Path to Execution Engine server's socket file"; + +const PRE_STATE_HASH_ARG_NAME: &str = "pre-state-hash"; +const PRE_STATE_HASH_ARG_SHORT: &str = "p"; +const PRE_STATE_HASH_ARG_VALUE_NAME: &str = "HEX-ENCODED HASH"; +const PRE_STATE_HASH_ARG_HELP: &str = + "Pre-state hash; the output of running the 'state-initializer' executable"; + +const THREAD_COUNT_ARG_NAME: &str = "threads"; +const THREAD_COUNT_ARG_SHORT: &str = "t"; +const THREAD_COUNT_ARG_DEFAULT: &str = "8"; +const THREAD_COUNT_ARG_VALUE_NAME: &str = "NUM"; +const THREAD_COUNT_ARG_HELP: &str = "Worker thread count"; + +const REQUEST_COUNT_ARG_NAME: &str = "requests"; +const REQUEST_COUNT_ARG_SHORT: &str = "r"; +const REQUEST_COUNT_ARG_DEFAULT: &str = "100"; +const REQUEST_COUNT_ARG_VALUE_NAME: &str = "NUM"; +const REQUEST_COUNT_ARG_HELP: &str = "Total number of 'ExecuteRequest's to send"; + +const TRANSFER_MODE_ARG_NAME: &str = "transfer-mode"; +const TRANSFER_MODE_ARG_SHORT: &str = "m"; +const TRANSFER_MODE_ARG_DEFAULT: &str = "WASMLESS"; +const TRANSFER_MODE_ARG_VALUE_NAME: &str = "&str"; +const TRANSFER_MODE_ARG_HELP: &str = "Transfer mode [WASMLESS|WASM]"; + +const CONTRACT_NAME: &str = "transfer_to_existing_account.wasm"; +const THREAD_PREFIX: &str = "client-worker-"; +const ARG_AMOUNT: &str = "amount"; +const ARG_TARGET: &str = "target"; + +fn socket_arg() -> Arg<'static, 'static> { + Arg::with_name(SOCKET_ARG_NAME) + .long(SOCKET_ARG_NAME) + .short(SOCKET_ARG_SHORT) + .required(true) + .value_name(SOCKET_ARG_VALUE_NAME) + .help(SOCKET_ARG_HELP) +} + +fn pre_state_hash_arg() -> Arg<'static, 'static> { + Arg::with_name(PRE_STATE_HASH_ARG_NAME) + .long(PRE_STATE_HASH_ARG_NAME) + .short(PRE_STATE_HASH_ARG_SHORT) + .required(true) + .value_name(PRE_STATE_HASH_ARG_VALUE_NAME) + .help(PRE_STATE_HASH_ARG_HELP) +} + +fn thread_count_arg() -> Arg<'static, 'static> { + Arg::with_name(THREAD_COUNT_ARG_NAME) + .long(THREAD_COUNT_ARG_NAME) + .short(THREAD_COUNT_ARG_SHORT) + .default_value(THREAD_COUNT_ARG_DEFAULT) + .value_name(THREAD_COUNT_ARG_VALUE_NAME) + .help(THREAD_COUNT_ARG_HELP) +} + +fn request_count_arg() -> Arg<'static, 'static> { + Arg::with_name(REQUEST_COUNT_ARG_NAME) + .long(REQUEST_COUNT_ARG_NAME) + .short(REQUEST_COUNT_ARG_SHORT) + .default_value(REQUEST_COUNT_ARG_DEFAULT) + .value_name(REQUEST_COUNT_ARG_VALUE_NAME) + .help(REQUEST_COUNT_ARG_HELP) +} + +fn transfer_mode_arg() -> Arg<'static, 'static> { + Arg::with_name(TRANSFER_MODE_ARG_NAME) + .long(TRANSFER_MODE_ARG_NAME) + .short(TRANSFER_MODE_ARG_SHORT) + .default_value(TRANSFER_MODE_ARG_DEFAULT) + .value_name(TRANSFER_MODE_ARG_VALUE_NAME) + .help(TRANSFER_MODE_ARG_HELP) +} + +struct Args { + socket: String, + pre_state_hash: Vec, + thread_count: usize, + request_count: usize, + transfer_mode: TransferMode, +} + +impl Args { + fn new() -> Self { + let arg_matches = App::new(APP_NAME) + .version(crate_version!()) + .about(ABOUT) + .arg(socket_arg()) + .arg(pre_state_hash_arg()) + .arg(thread_count_arg()) + .arg(request_count_arg()) + .arg(transfer_mode_arg()) + .get_matches(); + + let socket = arg_matches + .value_of(SOCKET_ARG_NAME) + .expect("Expected path to socket file") + .to_string(); + let pre_state_hash = arg_matches + .value_of(PRE_STATE_HASH_ARG_NAME) + .map(profiling::parse_hash) + .expect("Expected a pre-state hash"); + let thread_count = arg_matches + .value_of(THREAD_COUNT_ARG_NAME) + .map(profiling::parse_count) + .expect("Expected thread count"); + let request_count = arg_matches + .value_of(REQUEST_COUNT_ARG_NAME) + .map(profiling::parse_count) + .expect("Expected request count"); + let transfer_mode = arg_matches + .value_of(TRANSFER_MODE_ARG_NAME) + .map(profiling::parse_transfer_mode) + .expect("Expected transfer mode"); + + Args { + socket, + pre_state_hash, + thread_count, + request_count, + transfer_mode, + } + } +} + +/// Sent via a channel to the worker thread. +enum Message { + /// Instruction to handle the wrapped request. + Run { + request_num: usize, + request: ExecuteRequest, + }, + /// Instruction to stop processing any further requests. + Close, +} + +/// A `Worker` represents a thread that waits for `ExecuteRequest`s to arrive on one channel, uses +/// the EE client to send them to the EE server, and sends the duration for receiving the response +/// on another channel. +struct Worker { + handle: Option>, +} + +impl Worker { + fn new( + id: usize, + message_receiver: Receiver, + result_sender: Sender, + client: Arc, + ) -> Self { + let thread_name = format!("{}{}", THREAD_PREFIX, id); + let handle = thread::Builder::new() + .name(thread_name.clone()) + .spawn(move || loop { + let message = message_receiver + .recv() + .expect("Expected to receive a message"); + match message { + Message::Run { + request_num, + request, + } => Self::do_work(request_num, request, &thread_name, &result_sender, &client), + Message::Close => { + break; + } + }; + }) + .expect("Expected to spawn worker"); + + Worker { + handle: Some(handle), + } + } + + fn do_work( + request_num: usize, + request: ExecuteRequest, + thread_name: &str, + result_sender: &Sender, + client: &ExecutionEngineServiceClient, + ) { + info!( + "Client sending 'execute' request {} on {}", + request_num, thread_name + ); + let start = Instant::now(); + let response = client + .execute(RequestOptions::new(), request) + .wait_drop_metadata() + .expect("Expected ExecuteResponse"); + let duration = Instant::now() - start; + + let deploy_result = response + .get_success() + .get_deploy_results() + .get(0) + .expect("Expected single deploy result"); + if !deploy_result.has_execution_result() { + panic!("Expected ExecutionResult, got {:?} instead", deploy_result); + } + if deploy_result.get_execution_result().has_error() { + panic!( + "Expected successful execution result, but instead got: {:?}", + deploy_result.get_execution_result().get_error(), + ); + } + + info!( + "Client received successful response {} on {} in {:?}", + request_num, thread_name, duration + ); + + result_sender + .send(duration) + .expect("Expected to send result"); + } + + /// Takes the value out of the option in the `handle` field, leaving a `None` in its place. + fn take(&mut self) -> Option> { + self.handle.take() + } +} + +/// Wrapper around `crossbeam_channel::Sender`, used to restrict its API. +pub struct MessageSender(crossbeam_channel::Sender); + +impl MessageSender { + /// Blocks the current thread until a message is sent or the channel is disconnected. + pub fn send(&self, request_num: usize, request: ExecuteRequest) { + self.0 + .send(Message::Run { + request_num, + request, + }) + .expect("Expected to send message"); + } +} + +/// Wrapper around `crossbeam_channel::Receiver`, used to restrict its API. +pub struct ResultReceiver(crossbeam_channel::Receiver); + +impl ResultReceiver { + /// A blocking iterator over messages in the channel. + pub fn iter(&self) -> Iter { + self.0.iter() + } +} + +/// A thread pool intended to run the client. +pub struct ClientPool { + workers: Vec, + message_sender: crossbeam_channel::Sender, + result_receiver: crossbeam_channel::Receiver, +} + +impl ClientPool { + /// Creates a new thread pool with `size` worker threads associated with it. + pub fn new(size: usize, client: Arc) -> Self { + assert!(size > 0); + let mut workers = Vec::with_capacity(size); + let (message_sender, message_receiver) = crossbeam_channel::unbounded(); + let (result_sender, result_receiver) = crossbeam_channel::unbounded(); + for id in 0..size { + let message_receiver = message_receiver.clone(); + let result_sender = result_sender.clone(); + let client = Arc::clone(&client); + workers.push(Worker::new(id, message_receiver, result_sender, client)); + } + ClientPool { + workers, + message_sender, + result_receiver, + } + } + + /// Returns the sending side of the channel used to convey `ExecuteRequest`s to the client. + pub fn message_sender(&self) -> MessageSender { + MessageSender(self.message_sender.clone()) + } + + /// Returns the receiving side of the channel used to convey `Duration`s from the worker. + pub fn result_receiver(&self) -> ResultReceiver { + ResultReceiver(self.result_receiver.clone()) + } +} + +impl Drop for ClientPool { + fn drop(&mut self) { + for _ in &self.workers { + self.message_sender + .send(Message::Close) + .expect("Expected to send Close"); + } + + for worker in &mut self.workers { + if let Some(handle) = worker.take() { + handle.join().expect("Expected to join worker"); + } + } + } +} + +fn new_execute_request(args: &Args) -> ExecuteRequest { + let account_1_addr = profiling::account_1_account_hash(); + let transfer_args = runtime_args! { ARG_TARGET => profiling::account_2_account_hash(), ARG_AMOUNT => U512::one() }; + let deploy_item = match args.transfer_mode { + TransferMode::WASM => DeployItemBuilder::new() + .with_address(account_1_addr) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }) + .with_session_code(CONTRACT_NAME, transfer_args) + .with_authorization_keys(&[account_1_addr]) + .build(), + TransferMode::WASMLESS => DeployItemBuilder::new() + .with_address(account_1_addr) + .with_empty_payment_bytes(runtime_args! {}) + .with_transfer_args(transfer_args) + .with_authorization_keys(&[account_1_addr]) + .build(), + }; + + ExecuteRequestBuilder::from_deploy_item(deploy_item) + .with_pre_state_hash(&args.pre_state_hash) + .build() + .into() +} + +fn main() { + env_logger::init(); + + let args = Args::new(); + let client_config = Default::default(); + let client = Arc::new( + ExecutionEngineServiceClient::new_plain_unix(args.socket.as_str(), client_config) + .expect("Expected to create Test Client"), + ); + let pool = ClientPool::new(args.thread_count, client); + + let message_sender = pool.message_sender(); + let result_receiver = pool.result_receiver(); + + let result_consumer = thread::Builder::new() + .name("result-consumer".to_string()) + .spawn(move || result_receiver.iter().collect::>()) + .expect("Expected to spawn result-consumer"); + + let execute_request = new_execute_request(&args); + let request_count = args.request_count; + let start = Instant::now(); + let message_producer = thread::Builder::new() + .name("message-producer".to_string()) + .spawn(move || { + for request_num in 0..request_count { + message_sender.send(request_num, execute_request.clone()); + } + }) + .expect("Expected to spawn message-producer"); + + message_producer + .join() + .expect("Expected to join message-producer"); + + drop(pool); + + let durations = result_consumer + .join() + .expect("Expected to join response-consumer"); + let overall_duration = Instant::now() - start; + + assert_eq!(durations.len(), args.request_count); + + let mean_duration = Duration::sum(durations.iter()) / durations.len() as u32; + println!( + "Server handled {} requests in {:?} with an average response time of {:?}", + args.request_count, overall_duration, mean_duration + ); +} diff --git a/grpc/tests/src/profiling/concurrent_executor.sh b/grpc/tests/src/profiling/concurrent_executor.sh new file mode 100755 index 0000000000..a3218da933 --- /dev/null +++ b/grpc/tests/src/profiling/concurrent_executor.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash + +set -eu -o pipefail + +SERVER=casperlabs-engine-grpc-server +CLIENT=concurrent-executor +EE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." >/dev/null 2>&1 && pwd)" +THIS_SCRIPT="$(basename ${BASH_SOURCE[0]})" +TEMP_DIR=$(mktemp -d -t ${CLIENT}-XXXXX) +DATA_DIR="${TEMP_DIR}/DataDir" +SOCKET="${TEMP_DIR}/Socket" +MIN_THREADS=1 +MAX_THREADS=100 +MIN_MESSAGES=1 +MAX_MESSAGES=1000000 + +print_usage_and_exit() { + printf "USAGE:\n" + printf " %s [FLAGS]\n\n" "$THIS_SCRIPT" + printf "ARGS:\n" + printf " Number of threads for the server threadpool [%d-%d]\n" $MIN_THREADS $MAX_THREADS + printf " Number of threads for the client threadpool [%d-%d]\n" $MIN_THREADS $MAX_THREADS + printf " Total number of messages the client should send [%d-%d]\n\n" $MIN_MESSAGES $MAX_MESSAGES + printf "FLAGS:\n" + printf " -z Use system contracts instead of host-side logic for Mint, Proof of Stake and Standard Payment\n\n" + exit 127 +} + +validate_and_assign_arg() { + local ARG=$1 + local MIN=$2 + local MAX=$3 + local -n OUT_VAR=$4 + [[ $1 != *[^0-9]* && $1 -ge $MIN && $1 -le $MAX ]] || print_usage_and_exit + OUT_VAR=$ARG +} + +validate_and_assign_flag() { + local ARG=$1 + local -n OUT_VAR=$2 + [[ $1 == "-z" ]] || print_usage_and_exit + OUT_VAR=$ARG +} + +build_contracts_and_run_state_initializer() { + cd $EE_DIR + make build-contracts-rs + cd engine-tests/ + PRE_STATE_HASH=$(cargo run --release --bin=state-initializer -- --data-dir=$DATA_DIR) +} + +run_server() { + cd $EE_DIR + cargo build --release --bin=$SERVER + target/release/$SERVER $SOCKET --data-dir=$DATA_DIR --threads=$SERVER_THREAD_COUNT $USE_SYSTEM_CONTRACTS & + SERVER_PID=$! +} + +run_client() { + cd ${EE_DIR}/engine-tests/ + CLIENT_OUTPUT=$(cargo run --release --bin=$CLIENT -- \ + --socket=$SOCKET --pre-state-hash=$PRE_STATE_HASH --threads=$CLIENT_THREAD_COUNT --requests=$MESSAGE_COUNT) +} + +kill_server() { + kill -SIGINT $SERVER_PID + wait $SERVER_PID +} + +clean_up() { + rm -rf $TEMP_DIR +} + +trap clean_up EXIT + +if [[ $# -eq 3 ]]; then + validate_and_assign_arg $1 $MIN_THREADS $MAX_THREADS SERVER_THREAD_COUNT + validate_and_assign_arg $2 $MIN_THREADS $MAX_THREADS CLIENT_THREAD_COUNT + validate_and_assign_arg $3 $MIN_MESSAGES $MAX_MESSAGES MESSAGE_COUNT + USE_SYSTEM_CONTRACTS= +elif [[ $# -eq 4 ]]; then + validate_and_assign_arg $1 $MIN_THREADS $MAX_THREADS SERVER_THREAD_COUNT + validate_and_assign_arg $2 $MIN_THREADS $MAX_THREADS CLIENT_THREAD_COUNT + validate_and_assign_arg $3 $MIN_MESSAGES $MAX_MESSAGES MESSAGE_COUNT + validate_and_assign_flag $4 USE_SYSTEM_CONTRACTS +else + print_usage_and_exit +fi + +build_contracts_and_run_state_initializer +run_server +run_client +kill_server + +printf "\nWith %d Server threads and %d Client threads, %s\n\n" $SERVER_THREAD_COUNT $CLIENT_THREAD_COUNT "$CLIENT_OUTPUT" diff --git a/grpc/tests/src/profiling/host_function_metrics.rs b/grpc/tests/src/profiling/host_function_metrics.rs new file mode 100644 index 0000000000..5c75c445ce --- /dev/null +++ b/grpc/tests/src/profiling/host_function_metrics.rs @@ -0,0 +1,332 @@ +//! This executable is for outputting metrics on each of the EE host functions. +//! +//! In order to set up the required global state, the `state-initializer` should have been run +//! first. + +use std::{ + collections::BTreeMap, + env, + fs::{self, File}, + io::{self, Write}, + iter, + path::{Path, PathBuf}, + process::Command, + str::FromStr, +}; + +use clap::{crate_version, App, Arg}; +use log::LevelFilter; +use rand::{self, Rng}; +use serde_json::Value; + +use engine_test_support::internal::{ + DeployItemBuilder, ExecuteRequestBuilder, LmdbWasmTestBuilder, +}; +use node::contract_core::engine_state::EngineConfig; +use node::contract_shared::logging::{self, Settings}; +use types::{runtime_args, ApiError, RuntimeArgs}; + +use casperlabs_engine_tests::profiling; + +const ABOUT: &str = + "Executes a contract which logs metrics for all host functions. Note that the \ + 'state-initializer' executable should be run first to set up the required global state."; + +const EXECUTE_AS_SUBPROCESS_ARG: &str = "execute-as-subprocess"; + +const ROOT_HASH_ARG_NAME: &str = "root-hash"; +const ROOT_HASH_ARG_VALUE_NAME: &str = "HEX-ENCODED HASH"; +const ROOT_HASH_ARG_HELP: &str = + "Initial root hash; the output of running the 'state-initializer' executable"; + +const REPETITIONS_ARG_NAME: &str = "repetitions"; +const REPETITIONS_ARG_SHORT: &str = "r"; +const REPETITIONS_ARG_DEFAULT: &str = "10000"; +const REPETITIONS_ARG_VALUE_NAME: &str = "NUM"; +const REPETITIONS_ARG_HELP: &str = "Number of repetitions of each host function call"; + +const OUTPUT_DIR_ARG_NAME: &str = "output-dir"; +const OUTPUT_DIR_ARG_SHORT: &str = "o"; +const OUTPUT_DIR_ARG_VALUE_NAME: &str = "DIR"; +const OUTPUT_DIR_ARG_HELP: &str = + "Path to output directory. It will be created if it doesn't exist. If unspecified, the \ + current working directory will be used"; + +const HOST_FUNCTION_METRICS_CONTRACT: &str = "host_function_metrics.wasm"; +const PAYMENT_AMOUNT: u64 = profiling::ACCOUNT_1_INITIAL_AMOUNT - 1_000_000_000; +const EXPECTED_REVERT_VALUE: u16 = 10; +const CSV_HEADER: &str = "args,n_exec,total_elapsed_time"; +const ARG_AMOUNT: &str = "amount"; +const ARG_SEED: &str = "seed"; +const ARG_OTHERS: &str = "others"; + +fn execute_as_subprocess_arg() -> Arg<'static, 'static> { + Arg::with_name(EXECUTE_AS_SUBPROCESS_ARG) + .long(EXECUTE_AS_SUBPROCESS_ARG) + .hidden(true) +} + +fn root_hash_arg() -> Arg<'static, 'static> { + Arg::with_name(ROOT_HASH_ARG_NAME) + .value_name(ROOT_HASH_ARG_VALUE_NAME) + .help(ROOT_HASH_ARG_HELP) +} + +fn repetitions_arg() -> Arg<'static, 'static> { + Arg::with_name(REPETITIONS_ARG_NAME) + .long(REPETITIONS_ARG_NAME) + .short(REPETITIONS_ARG_SHORT) + .default_value(REPETITIONS_ARG_DEFAULT) + .value_name(REPETITIONS_ARG_VALUE_NAME) + .help(REPETITIONS_ARG_HELP) +} + +fn output_dir_arg() -> Arg<'static, 'static> { + Arg::with_name(OUTPUT_DIR_ARG_NAME) + .long(OUTPUT_DIR_ARG_NAME) + .short(OUTPUT_DIR_ARG_SHORT) + .value_name(OUTPUT_DIR_ARG_VALUE_NAME) + .help(OUTPUT_DIR_ARG_HELP) +} + +#[derive(Debug)] +struct Args { + execute_as_subprocess: bool, + root_hash: Option, + repetitions: usize, + output_dir: PathBuf, + data_dir: PathBuf, +} + +impl Args { + fn new() -> Self { + let exe_name = profiling::exe_name(); + let data_dir_arg = profiling::data_dir_arg(); + let arg_matches = App::new(&exe_name) + .version(crate_version!()) + .about(ABOUT) + .arg(execute_as_subprocess_arg()) + .arg(root_hash_arg()) + .arg(repetitions_arg()) + .arg(output_dir_arg()) + .arg(data_dir_arg) + .get_matches(); + let execute_as_subprocess = arg_matches.is_present(EXECUTE_AS_SUBPROCESS_ARG); + let root_hash = arg_matches + .value_of(ROOT_HASH_ARG_NAME) + .map(ToString::to_string); + let repetitions = arg_matches + .value_of(REPETITIONS_ARG_NAME) + .map(profiling::parse_count) + .expect("should have repetitions"); + let output_dir = match arg_matches.value_of(OUTPUT_DIR_ARG_NAME) { + Some(dir) => PathBuf::from_str(dir).expect("Expected a valid unicode path"), + None => env::current_dir().expect("Expected to be able to access current working dir"), + }; + let data_dir = profiling::data_dir(&arg_matches); + Args { + execute_as_subprocess, + root_hash, + repetitions, + output_dir, + data_dir, + } + } +} + +/// Executes the host-function-metrics contract repeatedly to generate metrics in stdout. +fn run_test(root_hash: Vec, repetitions: usize, data_dir: &Path) { + let log_settings = Settings::new(LevelFilter::Warn).with_metrics_enabled(true); + let _ = logging::initialize(log_settings); + + let account_1_account_hash = profiling::account_1_account_hash(); + let account_2_account_hash = profiling::account_2_account_hash(); + + let engine_config = EngineConfig::new() + .with_use_system_contracts(cfg!(feature = "use-system-contracts")) + .with_enable_bonding(cfg!(feature = "enable-bonding")); + + let mut test_builder = LmdbWasmTestBuilder::open(data_dir, engine_config, root_hash); + + let mut rng = rand::thread_rng(); + + for _ in 0..repetitions { + let seed: u64 = rng.gen(); + let random_bytes_length: usize = rng.gen_range(0, 10_000); + let mut random_bytes = vec![0_u8; random_bytes_length]; + rng.fill(random_bytes.as_mut_slice()); + + let deploy = DeployItemBuilder::new() + .with_address(account_1_account_hash) + .with_deploy_hash(rng.gen()) + .with_session_code( + HOST_FUNCTION_METRICS_CONTRACT, + runtime_args! { + ARG_SEED => seed, + ARG_OTHERS => (random_bytes, account_1_account_hash, account_2_account_hash), + }, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => PAYMENT_AMOUNT }) + .with_authorization_keys(&[account_1_account_hash]) + .build(); + let exec_request = ExecuteRequestBuilder::new() + .push_deploy(deploy.clone()) + .build(); + + test_builder.exec(exec_request); + // Should revert with User error 10. + let error_msg = test_builder + .exec_error_message(0) + .expect("should have error message"); + assert!( + error_msg.contains(&format!("{:?}", ApiError::User(EXPECTED_REVERT_VALUE))), + error_msg + ); + } +} + +#[derive(Debug)] +struct Metrics { + duration: String, + others: BTreeMap, +} + +fn gather_metrics(stdout: String) -> BTreeMap> { + const PAYLOAD_KEY: &str = "payload="; + const DESCRIPTION_KEY: &str = "description"; + const HOST_FUNCTION_PREFIX: &str = "host_function_"; + const PROPERTIES_KEY: &str = "properties"; + const DURATION_KEY: &str = "duration_in_seconds"; + const MESSAGE_KEY: &str = "message"; + const MESSAGE_TEMPLATE_KEY: &str = "message_template"; + + let mut result = BTreeMap::new(); + + for line in stdout.lines() { + if let Some(index) = line.find(PAYLOAD_KEY) { + let (_, payload_slice) = line.split_at(index + PAYLOAD_KEY.len()); + let mut payload = + serde_json::from_str::(payload_slice).expect("payload should parse as JSON"); + + let description = payload + .get_mut(DESCRIPTION_KEY) + .expect("payload should have description field") + .take(); + let function_id = description + .as_str() + .expect("description field should parse as string") + .split(' ') + .next() + .expect("description field should consist of function name followed by a space"); + if !function_id.starts_with(HOST_FUNCTION_PREFIX) { + continue; + } + let function_name = function_id + .split_at(HOST_FUNCTION_PREFIX.len()) + .1 + .to_string(); + + let metrics_vec = result.entry(function_name).or_insert_with(Vec::new); + + let mut properties: BTreeMap = serde_json::from_value( + payload + .get_mut(PROPERTIES_KEY) + .expect("payload should have properties field") + .take(), + ) + .expect("properties should parse as pairs of strings"); + + let duration = properties + .remove(DURATION_KEY) + .expect("properties should have a duration entry"); + let _ = properties.remove(MESSAGE_KEY); + let _ = properties.remove(MESSAGE_TEMPLATE_KEY); + + let metrics = Metrics { + duration, + others: properties, + }; + metrics_vec.push(metrics); + } + } + + result +} + +fn generate_csv(function_name: String, metrics_vec: Vec, output_dir: &Path) { + let file_path = output_dir.join(format!("{}.csv", function_name)); + let mut file = File::create(&file_path) + .unwrap_or_else(|_| panic!("should create {}", file_path.display())); + + writeln!(file, "{}", CSV_HEADER) + .unwrap_or_else(|_| panic!("should write to {}", file_path.display())); + + for metrics in metrics_vec { + write!(file, "\"(").unwrap_or_else(|_| panic!("should write to {}", file_path.display())); + for (_metric_key, metric_value) in metrics.others { + write!(file, "{},", metric_value) + .unwrap_or_else(|_| panic!("should write to {}", file_path.display())); + } + writeln!(file, ")\",1,{}", metrics.duration) + .unwrap_or_else(|_| panic!("should write to {}", file_path.display())); + } +} + +fn main() { + let args = Args::new(); + + // If the required initial root hash wasn't passed as a command line arg, expect to read it in + // from stdin to allow for it to be piped from the output of 'state-initializer'. + let (root_hash, root_hash_read_from_stdin) = match args.root_hash { + Some(root_hash) => (root_hash, false), + None => { + let mut input = String::new(); + let _ = io::stdin().read_line(&mut input); + (input.trim_end().to_string(), true) + } + }; + + // We're running as a subprocess - execute the test to output the metrics to stdout. + if args.execute_as_subprocess { + return run_test( + profiling::parse_hash(&root_hash), + args.repetitions, + &args.data_dir, + ); + } + + // We're running as the top-level process - invoke the current exe as a subprocess to capture + // its stdout. + let subprocess_flag = format!("--{}", EXECUTE_AS_SUBPROCESS_ARG); + let mut subprocess_args = env::args().chain(iter::once(subprocess_flag)); + let mut subprocess = Command::new( + subprocess_args + .next() + .expect("should get current executable's full path"), + ); + subprocess.args(subprocess_args); + if root_hash_read_from_stdin { + subprocess.arg(root_hash); + } + + let subprocess_output = subprocess + .output() + .expect("should run current executable as a subprocess"); + + let stdout = String::from_utf8(subprocess_output.stdout).expect("should be valid UTF-8"); + if !subprocess_output.status.success() { + let stderr = String::from_utf8(subprocess_output.stderr).expect("should be valid UTF-8"); + panic!( + "\nFailed to execute as subprocess:\n{}\n\n{}\n\n", + stdout, stderr + ); + } + + let all_metrics = gather_metrics(stdout); + let output_dir = &args.output_dir; + fs::create_dir_all(output_dir) + .unwrap_or_else(|_| panic!("should create {}", output_dir.display())); + for (function_id, metrics_vec) in all_metrics { + generate_csv(function_id, metrics_vec, &args.output_dir); + } +} diff --git a/grpc/tests/src/profiling/mod.rs b/grpc/tests/src/profiling/mod.rs new file mode 100644 index 0000000000..2572451bd7 --- /dev/null +++ b/grpc/tests/src/profiling/mod.rs @@ -0,0 +1,77 @@ +use std::{env, path::PathBuf, str::FromStr}; + +use clap::{Arg, ArgMatches}; + +use engine_test_support::DEFAULT_ACCOUNT_INITIAL_BALANCE; +use types::{account::AccountHash, U512}; + +const DATA_DIR_ARG_NAME: &str = "data-dir"; +const DATA_DIR_ARG_SHORT: &str = "d"; +const DATA_DIR_ARG_LONG: &str = "data-dir"; +const DATA_DIR_ARG_VALUE_NAME: &str = "PATH"; +const DATA_DIR_ARG_HELP: &str = "Directory in which persistent data is stored [default: current \ + working directory]"; + +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +pub const ACCOUNT_1_INITIAL_AMOUNT: u64 = DEFAULT_ACCOUNT_INITIAL_BALANCE - 1_000_000_000; +const ACCOUNT_2_ADDR: AccountHash = AccountHash::new([2u8; 32]); + +pub enum TransferMode { + WASM, + WASMLESS, +} + +pub fn exe_name() -> String { + env::current_exe() + .expect("Expected to read current executable's name") + .file_stem() + .expect("Expected a file name for the current executable") + .to_str() + .expect("Expected valid unicode for the current executable's name") + .to_string() +} + +pub fn data_dir_arg() -> Arg<'static, 'static> { + Arg::with_name(DATA_DIR_ARG_NAME) + .short(DATA_DIR_ARG_SHORT) + .long(DATA_DIR_ARG_LONG) + .value_name(DATA_DIR_ARG_VALUE_NAME) + .help(DATA_DIR_ARG_HELP) + .takes_value(true) +} + +pub fn data_dir(arg_matches: &ArgMatches) -> PathBuf { + match arg_matches.value_of(DATA_DIR_ARG_NAME) { + Some(dir) => PathBuf::from_str(dir).expect("Expected a valid unicode path"), + None => env::current_dir().expect("Expected to be able to access current working dir"), + } +} + +pub fn parse_hash(encoded_hash: &str) -> Vec { + base16::decode(encoded_hash).expect("Expected a valid, hex-encoded hash") +} + +pub fn parse_count(count_as_str: &str) -> usize { + let count: usize = count_as_str.parse().expect("Expected an integral count"); + assert!(count > 0, "Expected count > 0"); + count +} + +pub fn parse_transfer_mode(transfer_mode: &str) -> TransferMode { + match transfer_mode { + "WASM" => TransferMode::WASM, + _ => TransferMode::WASMLESS, + } +} + +pub fn account_1_account_hash() -> AccountHash { + ACCOUNT_1_ADDR +} + +pub fn account_1_initial_amount() -> U512 { + ACCOUNT_1_INITIAL_AMOUNT.into() +} + +pub fn account_2_account_hash() -> AccountHash { + ACCOUNT_2_ADDR +} diff --git a/grpc/tests/src/profiling/perf.sh b/grpc/tests/src/profiling/perf.sh new file mode 100755 index 0000000000..75207d4c74 --- /dev/null +++ b/grpc/tests/src/profiling/perf.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +set -eu -o pipefail + +RED='\033[0;31m' +CYAN='\033[0;36m' +NO_COLOR='\033[0m' +EE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." >/dev/null 2>&1 && pwd)" +TEMP_DIR=$(mktemp -d -t simple-transfer-perf-XXXXX) + +check_for_perf() { + if ! [[ -x "$(command -v perf)" ]]; then + printf "${RED}perf not installed${NO_COLOR}\n\n" + printf "For Debian, try:\n" + printf "${CYAN}sudo apt install linux-tools-common linux-tools-generic linux-tools-$(uname -r)${NO_COLOR}\n\n" + printf "For Redhat, try:\n" + printf "${CYAN}sudo yum install perf${NO_COLOR}\n\n" + exit 127 + fi +} + +run_perf() { + cd $EE_DIR + make build-contracts + cd engine-tests/ + cargo build --release --bin state-initializer + cargo build --release --bin simple-transfer + TARGET_DIR="${EE_DIR}/target/release" + DATA_DIR_ARG="--data-dir=../target" + ${TARGET_DIR}/state-initializer ${DATA_DIR_ARG} | perf record -g --call-graph dwarf ${TARGET_DIR}/simple-transfer ${DATA_DIR_ARG} + mv perf.data ${TEMP_DIR} +} + +check_or_clone_flamegraph() { + FLAMEGRAPH_DIR="/tmp/FlameGraph" + export PATH=${FLAMEGRAPH_DIR}:${PATH} + if ! [[ -x "$(command -v stackcollapse-perf.pl)" ]] || ! [[ -x "$(command -v flamegraph.pl)" ]]; then + rm -rf ${FLAMEGRAPH_DIR} + git clone --depth=1 https://github.com/brendangregg/FlameGraph ${FLAMEGRAPH_DIR} + fi +} + +create_flamegraph() { + FLAMEGRAPH="${TEMP_DIR}/flame.svg" + printf "Creating flamegraph at ${FLAMEGRAPH}\n" + cd ${TEMP_DIR} + perf script | stackcollapse-perf.pl | flamegraph.pl > ${FLAMEGRAPH} + x-www-browser ${FLAMEGRAPH} +} + +check_for_perf +run_perf +check_or_clone_flamegraph +create_flamegraph diff --git a/grpc/tests/src/profiling/simple_transfer.rs b/grpc/tests/src/profiling/simple_transfer.rs new file mode 100644 index 0000000000..c281bf310e --- /dev/null +++ b/grpc/tests/src/profiling/simple_transfer.rs @@ -0,0 +1,122 @@ +//! This executable is designed to be used to profile a single execution of a simple contract which +//! transfers an amount between two accounts. +//! +//! In order to set up the required global state for the transfer, the `state-initializer` should +//! have been run first. +//! +//! By avoiding setting up global state as part of this executable, it will allow profiling to be +//! done only on meaningful code, rather than including test setup effort in the profile results. + +use std::{env, io, path::PathBuf}; + +use clap::{crate_version, App, Arg}; + +use engine_test_support::internal::{ + DeployItemBuilder, ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_PAYMENT, +}; +use node::contract_core::engine_state::EngineConfig; +use types::{runtime_args, RuntimeArgs, U512}; + +use casperlabs_engine_tests::profiling; + +const ABOUT: &str = "Executes a simple contract which transfers an amount between two accounts. \ + Note that the 'state-initializer' executable should be run first to set up the required \ + global state."; + +const ROOT_HASH_ARG_NAME: &str = "root-hash"; +const ROOT_HASH_ARG_VALUE_NAME: &str = "HEX-ENCODED HASH"; +const ROOT_HASH_ARG_HELP: &str = + "Initial root hash; the output of running the 'state-initializer' executable"; + +const VERBOSE_ARG_NAME: &str = "verbose"; +const VERBOSE_ARG_SHORT: &str = "v"; +const VERBOSE_ARG_LONG: &str = "verbose"; +const VERBOSE_ARG_HELP: &str = "Display the transforms resulting from the contract execution"; + +const TRANSFER_AMOUNT: u64 = 1; + +fn root_hash_arg() -> Arg<'static, 'static> { + Arg::with_name(ROOT_HASH_ARG_NAME) + .value_name(ROOT_HASH_ARG_VALUE_NAME) + .help(ROOT_HASH_ARG_HELP) +} + +fn verbose_arg() -> Arg<'static, 'static> { + Arg::with_name(VERBOSE_ARG_NAME) + .short(VERBOSE_ARG_SHORT) + .long(VERBOSE_ARG_LONG) + .help(VERBOSE_ARG_HELP) +} + +#[derive(Debug)] +struct Args { + root_hash: Option>, + data_dir: PathBuf, + verbose: bool, +} + +impl Args { + fn new() -> Self { + let exe_name = profiling::exe_name(); + let data_dir_arg = profiling::data_dir_arg(); + let arg_matches = App::new(&exe_name) + .version(crate_version!()) + .about(ABOUT) + .arg(root_hash_arg()) + .arg(data_dir_arg) + .arg(verbose_arg()) + .get_matches(); + let root_hash = arg_matches + .value_of(ROOT_HASH_ARG_NAME) + .map(profiling::parse_hash); + let data_dir = profiling::data_dir(&arg_matches); + let verbose = arg_matches.is_present(VERBOSE_ARG_NAME); + Args { + root_hash, + data_dir, + verbose, + } + } +} + +fn main() { + let args = Args::new(); + + // If the required initial root hash wasn't passed as a command line arg, expect to read it in + // from stdin to allow for it to be piped from the output of 'state-initializer'. + let root_hash = args.root_hash.unwrap_or_else(|| { + let mut input = String::new(); + let _ = io::stdin().read_line(&mut input); + profiling::parse_hash(input.trim_end()) + }); + + let account_1_account_hash = profiling::account_1_account_hash(); + let account_2_account_hash = profiling::account_2_account_hash(); + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(account_1_account_hash) + .with_deploy_hash([1; 32]) + .with_session_code( + "simple_transfer.wasm", + runtime_args! { "target" =>account_2_account_hash, "amount" => U512::from(TRANSFER_AMOUNT) }, + ) + .with_empty_payment_bytes( runtime_args! { "amount" => *DEFAULT_PAYMENT}) + .with_authorization_keys(&[account_1_account_hash]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let engine_config = EngineConfig::new() + .with_use_system_contracts(cfg!(feature = "use-system-contracts")) + .with_enable_bonding(cfg!(feature = "enable-bonding")); + + let mut test_builder = LmdbWasmTestBuilder::open(&args.data_dir, engine_config, root_hash); + + test_builder.exec(exec_request).expect_success().commit(); + + if args.verbose { + println!("{:#?}", test_builder.get_transforms()); + } +} diff --git a/grpc/tests/src/profiling/state_initializer.rs b/grpc/tests/src/profiling/state_initializer.rs new file mode 100644 index 0000000000..6353c29c43 --- /dev/null +++ b/grpc/tests/src/profiling/state_initializer.rs @@ -0,0 +1,97 @@ +//! This executable is designed to be run to set up global state in preparation for running other +//! standalone test executable(s). This will allow profiling to be done on executables running only +//! meaningful code, rather than including test setup effort in the profile results. + +use std::{env, path::PathBuf}; + +use clap::{crate_version, App}; + +use engine_test_support::{ + internal::{ + utils, DeployItemBuilder, ExecuteRequestBuilder, LmdbWasmTestBuilder, ARG_AMOUNT, + DEFAULT_ACCOUNTS, DEFAULT_GENESIS_CONFIG_HASH, DEFAULT_PAYMENT, DEFAULT_PROTOCOL_VERSION, + DEFAULT_WASM_COSTS, MINT_INSTALL_CONTRACT, POS_INSTALL_CONTRACT, + STANDARD_PAYMENT_INSTALL_CONTRACT, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::{ + engine_config::EngineConfig, genesis::ExecConfig, run_genesis_request::RunGenesisRequest, +}; + +use casperlabs_engine_tests::profiling; +use types::{runtime_args, RuntimeArgs}; + +const ABOUT: &str = "Initializes global state in preparation for profiling runs. Outputs the root \ + hash from the commit response."; +const STATE_INITIALIZER_CONTRACT: &str = "state_initializer.wasm"; +const ARG_ACCOUNT1_HASH: &str = "account_1_account_hash"; +const ARG_ACCOUNT1_AMOUNT: &str = "account_1_amount"; +const ARG_ACCOUNT2_HASH: &str = "account_2_account_hash"; + +fn data_dir() -> PathBuf { + let exe_name = profiling::exe_name(); + let data_dir_arg = profiling::data_dir_arg(); + let arg_matches = App::new(&exe_name) + .version(crate_version!()) + .about(ABOUT) + .arg(data_dir_arg) + .get_matches(); + profiling::data_dir(&arg_matches) +} + +fn main() { + let data_dir = data_dir(); + + let genesis_account_hash = DEFAULT_ACCOUNT_ADDR; + let account_1_account_hash = profiling::account_1_account_hash(); + let account_1_initial_amount = profiling::account_1_initial_amount(); + let account_2_account_hash = profiling::account_2_account_hash(); + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_session_code( + STATE_INITIALIZER_CONTRACT, + runtime_args! { + ARG_ACCOUNT1_HASH => account_1_account_hash, + ARG_ACCOUNT1_AMOUNT => account_1_initial_amount, + ARG_ACCOUNT2_HASH => account_2_account_hash, + }, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[genesis_account_hash]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let engine_config = EngineConfig::new().with_use_system_contracts(true); + let mut builder = LmdbWasmTestBuilder::new_with_config(&data_dir, engine_config); + + let mint_installer_bytes = utils::read_wasm_file_bytes(MINT_INSTALL_CONTRACT); + let pos_installer_bytes = utils::read_wasm_file_bytes(POS_INSTALL_CONTRACT); + let standard_payment_installer_bytes = + utils::read_wasm_file_bytes(STANDARD_PAYMENT_INSTALL_CONTRACT); + let exec_config = ExecConfig::new( + mint_installer_bytes, + pos_installer_bytes, + standard_payment_installer_bytes, + DEFAULT_ACCOUNTS.clone(), + *DEFAULT_WASM_COSTS, + ); + let run_genesis_request = RunGenesisRequest::new( + *DEFAULT_GENESIS_CONFIG_HASH, + *DEFAULT_PROTOCOL_VERSION, + exec_config, + ); + + let post_state_hash = builder + .run_genesis(&run_genesis_request) + .exec(exec_request) + .expect_success() + .commit() + .get_post_state_hash(); + println!("{}", base16::encode_lower(&post_state_hash)); +} diff --git a/grpc/tests/src/test/check_transfer_success.rs b/grpc/tests/src/test/check_transfer_success.rs new file mode 100644 index 0000000000..e2147ff06b --- /dev/null +++ b/grpc/tests/src/test/check_transfer_success.rs @@ -0,0 +1,210 @@ +use core::convert::TryFrom; +use types::{runtime_args, RuntimeArgs, U512}; + +use engine_test_support::{ + Code, SessionBuilder, SessionTransferInfo, TestContextBuilder, DEFAULT_ACCOUNT_ADDR, + DEFAULT_ACCOUNT_INITIAL_BALANCE, +}; + +const ARG_AMOUNT: &str = "amount"; +const ARG_DESTINATION: &str = "destination"; +const DESTINATION_PURSE_ONE: &str = "destination_purse_one"; +const DESTINATION_PURSE_TWO: &str = "destination_purse_two"; +const TRANSFER_AMOUNT_ONE: &str = "transfer_amount_one"; +const TRANSFER_AMOUNT_TWO: &str = "transfer_amount_two"; +const TRANSFER_WASM: &str = "transfer_main_purse_to_new_purse.wasm"; +const TRANSFER_TO_TWO_PURSES: &str = "transfer_main_purse_to_two_purses.wasm"; +const NEW_PURSE_NAME: &str = "test_purse"; +const SECOND_PURSE_NAME: &str = "second_purse"; +const FIRST_TRANSFER_AMOUNT: u64 = 142; +const SECOND_TRANSFER_AMOUNT: u64 = 250; + +#[ignore] +#[test] +fn test_check_transfer_success_with_source_only() { + let mut test_context = TestContextBuilder::new() + .with_account( + DEFAULT_ACCOUNT_ADDR, + U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE), + ) + .build(); + + // Getting main purse URef to verify transfer + let source_purse = test_context + .main_purse_address(DEFAULT_ACCOUNT_ADDR) + .expect("main purse address"); + // Target purse doesn't exist yet, so only verifying removal from source + let maybe_target_purse = None; + let transfer_amount = U512::try_from(FIRST_TRANSFER_AMOUNT).expect("U512 from u64"); + let source_only_session_transfer_info = + SessionTransferInfo::new(source_purse, maybe_target_purse, transfer_amount); + + // Doing a transfer from main purse to create new purse and store URef under NEW_PURSE_NAME. + let session_code = Code::from(TRANSFER_WASM); + let session_args = runtime_args! { + ARG_DESTINATION => NEW_PURSE_NAME, + ARG_AMOUNT => transfer_amount + }; + let session = SessionBuilder::new(session_code, session_args) + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_check_transfer_success(source_only_session_transfer_info) + .build(); + test_context.run(session); +} + +#[ignore] +#[test] +#[should_panic] +fn test_check_transfer_success_with_source_only_errors() { + let mut test_context = TestContextBuilder::new() + .with_account( + DEFAULT_ACCOUNT_ADDR, + U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE), + ) + .build(); + + // Getting main purse Uref to verify transfer + let source_purse = test_context + .main_purse_address(DEFAULT_ACCOUNT_ADDR) + .expect("main purse address"); + let maybe_target_purse = None; + // Setup mismatch between transfer_amount performed and given to trigger assertion. + let transfer_amount = U512::try_from(FIRST_TRANSFER_AMOUNT).expect("U512 from u64"); + let wrong_transfer_amount = transfer_amount - U512::try_from(100u64).expect("U512 from 64"); + let source_only_session_transfer_info = + SessionTransferInfo::new(source_purse, maybe_target_purse, transfer_amount); + + // Doing a transfer from main purse to create new purse and store Uref under NEW_PURSE_NAME. + let session_code = Code::from(TRANSFER_WASM); + let session_args = runtime_args! { + ARG_DESTINATION => NEW_PURSE_NAME, + ARG_AMOUNT => wrong_transfer_amount + }; + // Handle expected assertion fail. + let session = SessionBuilder::new(session_code, session_args) + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_check_transfer_success(source_only_session_transfer_info) + .build(); + test_context.run(session); // will panic if transfer does not work +} + +#[ignore] +#[test] +fn test_check_transfer_success_with_source_and_target() { + let mut test_context = TestContextBuilder::new() + .with_account( + DEFAULT_ACCOUNT_ADDR, + U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE), + ) + .build(); + + // Getting main purse URef to verify transfer + let source_purse = test_context + .main_purse_address(DEFAULT_ACCOUNT_ADDR) + .expect("main purse address"); + + let maybe_target_purse = None; + let transfer_amount = U512::try_from(SECOND_TRANSFER_AMOUNT).expect("U512 from u64"); + let source_and_target_session_transfer_info = + SessionTransferInfo::new(source_purse, maybe_target_purse, transfer_amount); + + // Doing a transfer from main purse to create new purse and store URef under NEW_PURSE_NAME. + let session_code = Code::from(TRANSFER_WASM); + let session_args = runtime_args! { + ARG_DESTINATION => NEW_PURSE_NAME, + ARG_AMOUNT => transfer_amount + }; + let session = SessionBuilder::new(session_code, session_args) + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_check_transfer_success(source_and_target_session_transfer_info) + .build(); + test_context.run(session); + + // retrieve newly created purse URef + test_context + .query(DEFAULT_ACCOUNT_ADDR, &[NEW_PURSE_NAME]) + .expect("new purse should exist"); +} + +#[ignore] +#[test] +#[should_panic] +fn test_check_transfer_success_with_target_error() { + let mut test_context = TestContextBuilder::new() + .with_account( + DEFAULT_ACCOUNT_ADDR, + U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE), + ) + .build(); + + // Getting main purse URef to verify transfer + let source_purse = test_context + .main_purse_address(DEFAULT_ACCOUNT_ADDR) + .expect("main purse address"); + let maybe_target_purse = None; + + // Contract will transfer from main purse twice, into two different purses + // This call will create the purses, so we can get the URef to destination purses. + let transfer_one_amount = U512::try_from(FIRST_TRANSFER_AMOUNT).expect("U512 from u64"); + let transfer_two_amount = U512::try_from(SECOND_TRANSFER_AMOUNT).expect("U512 from u64"); + let main_purse_transfer_from_amount = transfer_one_amount + transfer_two_amount; + let source_only_session_transfer_info = SessionTransferInfo::new( + source_purse, + maybe_target_purse, + main_purse_transfer_from_amount, + ); + + // Will create two purses NEW_PURSE_NAME and SECOND_PURSE_NAME + let session_code = Code::from(TRANSFER_TO_TWO_PURSES); + let session_args = runtime_args! { + DESTINATION_PURSE_ONE => NEW_PURSE_NAME, + TRANSFER_AMOUNT_ONE => transfer_one_amount, + DESTINATION_PURSE_TWO => SECOND_PURSE_NAME, + TRANSFER_AMOUNT_TWO => transfer_two_amount, + }; + let session = SessionBuilder::new(session_code, session_args) + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_check_transfer_success(source_only_session_transfer_info) + .build(); + test_context.run(session); + + // get account purse by name via get_account() + let account = test_context + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("account"); + + let new_purse_address = account + .named_keys() + .get(NEW_PURSE_NAME) + .expect("value") + .into_uref() + .expect("uref"); + + let maybe_target_purse = Some(new_purse_address); // TODO: Put valid URef here + let source_and_target_session_transfer_info = SessionTransferInfo::new( + source_purse, + maybe_target_purse, + main_purse_transfer_from_amount, + ); + + // Same transfer as before, but with maybe_target_purse active for validating amount into purse + // The test for total pulled from main purse should not assert. + // The test for total into NEW_PURSE_NAME is only part of transfer and should assert. + let session_code = Code::from(TRANSFER_TO_TWO_PURSES); + let session_args = runtime_args! { + DESTINATION_PURSE_ONE => NEW_PURSE_NAME, + TRANSFER_AMOUNT_ONE => transfer_one_amount, + DESTINATION_PURSE_TWO => SECOND_PURSE_NAME, + TRANSFER_AMOUNT_TWO => transfer_two_amount, + }; + let session = SessionBuilder::new(session_code, session_args) + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_check_transfer_success(source_and_target_session_transfer_info) + .build(); + test_context.run(session); // will panic because maybe_target_purse balance isn't correct +} diff --git a/grpc/tests/src/test/contract_api/account/associated_keys.rs b/grpc/tests/src/test/contract_api/account/associated_keys.rs new file mode 100644 index 0000000000..af2621aa3d --- /dev/null +++ b/grpc/tests/src/test/contract_api/account/associated_keys.rs @@ -0,0 +1,85 @@ +use lazy_static::lazy_static; + +use engine_test_support::{ + internal::{ + ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{ + account::{AccountHash, Weight}, + runtime_args, RuntimeArgs, U512, +}; + +const CONTRACT_ADD_UPDATE_ASSOCIATED_KEY: &str = "add_update_associated_key.wasm"; +const CONTRACT_REMOVE_ASSOCIATED_KEY: &str = "remove_associated_key.wasm"; +const CONTRACT_TRANSFER_PURSE_TO_ACCOUNT: &str = "transfer_purse_to_account.wasm"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const ARG_ACCOUNT: &str = "account"; + +lazy_static! { + static ref ACCOUNT_1_INITIAL_FUND: U512 = *DEFAULT_PAYMENT * 10; +} + +#[ignore] +#[test] +fn should_manage_associated_key() { + // for a given account, should be able to add a new associated key and update + // that key + let mut builder = InMemoryWasmTestBuilder::default(); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { "target" => ACCOUNT_1_ADDR, "amount" => *ACCOUNT_1_INITIAL_FUND }, + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_ADD_UPDATE_ASSOCIATED_KEY, + runtime_args! { "account" => DEFAULT_ACCOUNT_ADDR, }, + ) + .build(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit(); + + builder.exec(exec_request_2).expect_success().commit(); + + let genesis_key = DEFAULT_ACCOUNT_ADDR; + + let account_1 = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should have account"); + + let gen_weight = account_1 + .get_associated_key_weight(genesis_key) + .expect("weight"); + + let expected_weight = Weight::new(2); + assert_eq!(*gen_weight, expected_weight, "unexpected weight"); + + let exec_request_3 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_REMOVE_ASSOCIATED_KEY, + runtime_args! { ARG_ACCOUNT => DEFAULT_ACCOUNT_ADDR, }, + ) + .build(); + + builder.exec(exec_request_3).expect_success().commit(); + + let account_1 = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should have account"); + + let new_weight = account_1.get_associated_key_weight(genesis_key); + + assert_eq!(new_weight, None, "key should be removed"); + + let is_error = builder.is_error(); + assert!(!is_error); +} diff --git a/grpc/tests/src/test/contract_api/account/authorized_keys.rs b/grpc/tests/src/test/contract_api/account/authorized_keys.rs new file mode 100644 index 0000000000..32fe33deda --- /dev/null +++ b/grpc/tests/src/test/contract_api/account/authorized_keys.rs @@ -0,0 +1,412 @@ +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, ARG_AMOUNT, + DEFAULT_PAYMENT, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::{engine_state, execution}; +use types::{ + account::{AccountHash, Weight}, + runtime_args, RuntimeArgs, +}; + +const CONTRACT_ADD_UPDATE_ASSOCIATED_KEY: &str = "add_update_associated_key.wasm"; +const CONTRACT_AUTHORIZED_KEYS: &str = "authorized_keys.wasm"; + +#[ignore] +#[test] +fn should_deploy_with_authorized_identity_key() { + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_AUTHORIZED_KEYS, + runtime_args! { + "key_management_threshold" => Weight::new(1), + "deploy_threshold" => Weight::new(1), + }, + ) + .build(); + // Basic deploy with single key + InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .expect_success(); +} + +#[ignore] +#[test] +fn should_raise_auth_failure_with_invalid_key() { + // tests that authorized keys that does not belong to account raises + // Error::Authorization + let key_1 = AccountHash::new([254; 32]); + assert_ne!(DEFAULT_ACCOUNT_ADDR, key_1); + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_empty_payment_bytes( + runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }, + ) + .with_session_code(CONTRACT_AUTHORIZED_KEYS, runtime_args! { "key_management_threshold" => Weight::new(1), "deploy_threshold" => Weight::new(1) }) + .with_deploy_hash([1u8; 32]) + .with_authorization_keys(&[key_1]) + .build(); + ExecuteRequestBuilder::from_deploy_item(deploy).build() + }; + + // Basic deploy with single key + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + let deploy_result = result + .builder() + .get_exec_response(0) + .expect("should have exec response") + .get(0) + .expect("should have at least one deploy result"); + + assert!( + deploy_result.has_precondition_failure(), + "{:?}", + deploy_result + ); + let message = format!("{}", deploy_result.as_error().unwrap()); + + assert_eq!(message, format!("{}", engine_state::Error::Authorization)) +} + +#[ignore] +#[test] +fn should_raise_auth_failure_with_invalid_keys() { + // tests that authorized keys that does not belong to account raises + // Error::Authorization + let key_1 = AccountHash::new([254; 32]); + let key_2 = AccountHash::new([253; 32]); + let key_3 = AccountHash::new([252; 32]); + assert_ne!(DEFAULT_ACCOUNT_ADDR, key_1); + assert_ne!(DEFAULT_ACCOUNT_ADDR, key_2); + assert_ne!(DEFAULT_ACCOUNT_ADDR, key_3); + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_empty_payment_bytes( + runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }, + ) + .with_session_code("authorized_keys.wasm", runtime_args! { "key_management_threshold" => Weight::new(1), "deploy_threshold" => Weight::new(1) }) + .with_deploy_hash([1u8; 32]) + .with_authorization_keys(&[key_2, key_1, key_3]) + .build(); + ExecuteRequestBuilder::from_deploy_item(deploy).build() + }; + + // Basic deploy with single key + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + let deploy_result = result + .builder() + .get_exec_response(0) + .expect("should have exec response") + .get(0) + .expect("should have at least one deploy result"); + + assert!(deploy_result.has_precondition_failure()); + let message = format!("{}", deploy_result.as_error().unwrap()); + + assert_eq!(message, format!("{}", engine_state::Error::Authorization)) +} + +#[ignore] +#[test] +fn should_raise_deploy_authorization_failure() { + // tests that authorized keys needs sufficient cumulative weight + let key_1 = AccountHash::new([254; 32]); + let key_2 = AccountHash::new([253; 32]); + let key_3 = AccountHash::new([252; 32]); + assert_ne!(DEFAULT_ACCOUNT_ADDR, key_1); + assert_ne!(DEFAULT_ACCOUNT_ADDR, key_2); + assert_ne!(DEFAULT_ACCOUNT_ADDR, key_3); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_ADD_UPDATE_ASSOCIATED_KEY, + runtime_args! { "account" => key_1, }, + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_ADD_UPDATE_ASSOCIATED_KEY, + runtime_args! { "account" => key_2, }, + ) + .build(); + let exec_request_3 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_ADD_UPDATE_ASSOCIATED_KEY, + runtime_args! { "account" => key_3, }, + ) + .build(); + // Deploy threshold is equal to 3, keymgmnt is still 1. + // Even after verifying weights and thresholds to not + // lock out the account, those values should work as + // account now has 1. identity key with weight=1 and + // a key with weight=2. + let exec_request_4 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_AUTHORIZED_KEYS, + runtime_args! { "key_management_threshold" => Weight::new(4), "deploy_threshold" => Weight::new(3) }, + ) + .build(); + // Basic deploy with single key + let result1 = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + // Reusing a test contract that would add new key + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .expect_success() + .commit() + .exec(exec_request_3) + .expect_success() + .commit() + // This should execute successfuly - change deploy and key management + // thresholds. + .exec(exec_request_4) + .expect_success() + .commit() + .finish(); + + let exec_request_5 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_AUTHORIZED_KEYS, + runtime_args! { "key_management_threshold" => Weight::new(5), "deploy_threshold" => Weight::new(4) }, //args + ) + .build(); + + // With deploy threshold == 3 using single secondary key + // with weight == 2 should raise deploy authorization failure. + let result2 = InMemoryWasmTestBuilder::from_result(result1) + .exec(exec_request_5) + .commit() + .finish(); + + { + let deploy_result = result2 + .builder() + .get_exec_response(0) + .expect("should have exec response") + .get(0) + .expect("should have at least one deploy result"); + + assert!(deploy_result.has_precondition_failure()); + let message = format!("{}", deploy_result.as_error().unwrap()); + assert!(message.contains(&format!( + "{}", + execution::Error::DeploymentAuthorizationFailure + ))) + } + let exec_request_6 = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_empty_payment_bytes( + runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }, + ) + // change deployment threshold to 4 + .with_session_code("authorized_keys.wasm", runtime_args! { "key_management_threshold" => Weight::new(6), "deploy_threshold" => Weight::new(5) }) + .with_deploy_hash([6u8; 32]) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR, key_1, key_2, key_3]) + .build(); + ExecuteRequestBuilder::from_deploy_item(deploy).build() + }; + // identity key (w: 1) and key_1 (w: 2) passes threshold of 3 + let result3 = InMemoryWasmTestBuilder::from_result(result2) + .exec(exec_request_6) + .expect_success() + .commit() + .finish(); + + let exec_request_7 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_AUTHORIZED_KEYS, + runtime_args! { "key_management_threshold" => Weight::new(0), "deploy_threshold" => Weight::new(0) }, //args + ) + .build(); + + // deployment threshold is now 4 + // failure: key_2 weight + key_1 weight < deployment threshold + let result4 = InMemoryWasmTestBuilder::from_result(result3) + .exec(exec_request_7) + .commit() + .finish(); + + { + let deploy_result = result4 + .builder() + .get_exec_response(0) + .expect("should have exec response") + .get(0) + .expect("should have at least one deploy result"); + + assert!(deploy_result.has_precondition_failure()); + let message = format!("{}", deploy_result.as_error().unwrap()); + assert!(message.contains(&format!( + "{}", + execution::Error::DeploymentAuthorizationFailure + ))) + } + + let exec_request_8 = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + // change deployment threshold to 4 + .with_session_code( + "authorized_keys.wasm", + runtime_args! { "key_management_threshold" => Weight::new(0), "deploy_threshold" => Weight::new(0) }, //args + ) + .with_deploy_hash([8u8; 32]) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR, key_1, key_2, key_3]) + .build(); + ExecuteRequestBuilder::from_deploy_item(deploy).build() + }; + + // success: identity key weight + key_1 weight + key_2 weight >= deployment + // threshold + InMemoryWasmTestBuilder::from_result(result4) + .exec(exec_request_8) + .commit() + .expect_success() + .finish(); +} + +#[ignore] +#[test] +fn should_authorize_deploy_with_multiple_keys() { + // tests that authorized keys needs sufficient cumulative weight + // and each of the associated keys is greater than threshold + + let key_1 = AccountHash::new([254; 32]); + let key_2 = AccountHash::new([253; 32]); + assert_ne!(DEFAULT_ACCOUNT_ADDR, key_1); + assert_ne!(DEFAULT_ACCOUNT_ADDR, key_2); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_ADD_UPDATE_ASSOCIATED_KEY, + runtime_args! { "account" => key_1, }, + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_ADD_UPDATE_ASSOCIATED_KEY, + runtime_args! { "account" => key_2, }, + ) + .build(); + // Basic deploy with single key + let result1 = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + // Reusing a test contract that would add new key + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .expect_success() + .commit() + .finish(); + + // key_1 (w: 2) key_2 (w: 2) each passes default threshold of 1 + + let exec_request_3 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_AUTHORIZED_KEYS, + runtime_args! { + "key_management_threshold" => Weight::new(0), + "deploy_threshold" => Weight::new(0), + }, + ) + .build(); + InMemoryWasmTestBuilder::from_result(result1) + .exec(exec_request_3) + .expect_success() + .commit(); +} + +#[ignore] +#[test] +fn should_not_authorize_deploy_with_duplicated_keys() { + // tests that authorized keys needs sufficient cumulative weight + // and each of the associated keys is greater than threshold + let key_1 = AccountHash::new([254; 32]); + assert_ne!(DEFAULT_ACCOUNT_ADDR, key_1); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_ADD_UPDATE_ASSOCIATED_KEY, + runtime_args! { "account" => key_1, }, + ) + .build(); + + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_AUTHORIZED_KEYS, + runtime_args! { "key_management_threshold" => Weight::new(4), "deploy_threshold" => Weight::new(3) }, + ) + .build(); + // Basic deploy with single key + let result1 = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + // Reusing a test contract that would add new key + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .expect_success() + .commit() + .finish(); + + let exec_request_3 = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_empty_payment_bytes( + runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }, + ) + .with_session_code("authorized_keys.wasm", runtime_args! { "key_management_threshold" => Weight::new(0), "deploy_threshold" => Weight::new(0) }) + .with_deploy_hash([3u8; 32]) + .with_authorization_keys(&[ + key_1, key_1, key_1, key_1, key_1, key_1, key_1, key_1, key_1, key_1, + ]) + .build(); + ExecuteRequestBuilder::from_deploy_item(deploy).build() + }; + // success: identity key weight + key_1 weight + key_2 weight >= deployment + // threshold + let final_result = InMemoryWasmTestBuilder::from_result(result1) + .exec(exec_request_3) + .commit() + .finish(); + let deploy_result = final_result + .builder() + .get_exec_response(0) + .expect("should have exec response") + .get(0) + .expect("should have at least one deploy result"); + + assert!( + deploy_result.has_precondition_failure(), + "{:?}", + deploy_result + ); + let message = format!("{}", deploy_result.as_error().unwrap()); + assert!(message.contains(&format!( + "{}", + execution::Error::DeploymentAuthorizationFailure + ))) +} diff --git a/grpc/tests/src/test/contract_api/account/key_management_thresholds.rs b/grpc/tests/src/test/contract_api/account/key_management_thresholds.rs new file mode 100644 index 0000000000..41623de1bb --- /dev/null +++ b/grpc/tests/src/test/contract_api/account/key_management_thresholds.rs @@ -0,0 +1,74 @@ +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, ARG_AMOUNT, + DEFAULT_PAYMENT, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{account::AccountHash, runtime_args, RuntimeArgs}; + +const CONTRACT_KEY_MANAGEMENT_THRESHOLDS: &str = "key_management_thresholds.wasm"; + +const ARG_STAGE: &str = "stage"; + +#[ignore] +#[test] +fn should_verify_key_management_permission_with_low_weight() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_KEY_MANAGEMENT_THRESHOLDS, + runtime_args! { ARG_STAGE => String::from("init") }, + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_KEY_MANAGEMENT_THRESHOLDS, + runtime_args! { ARG_STAGE => String::from("test-permission-denied") }, + ) + .build(); + InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .expect_success() + .commit(); +} + +#[ignore] +#[test] +fn should_verify_key_management_permission_with_sufficient_weight() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_KEY_MANAGEMENT_THRESHOLDS, + runtime_args! { ARG_STAGE => String::from("init") }, + ) + .build(); + let exec_request_2 = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + // This test verifies that all key management operations succeed + .with_session_code( + "key_management_thresholds.wasm", + runtime_args! { ARG_STAGE => String::from("test-key-mgmnt-succeed") }, + ) + .with_deploy_hash([2u8; 32]) + .with_authorization_keys(&[ + DEFAULT_ACCOUNT_ADDR, + // Key [42; 32] is created in init stage + AccountHash::new([42; 32]), + ]) + .build(); + ExecuteRequestBuilder::from_deploy_item(deploy).build() + }; + InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .expect_success() + .commit(); +} diff --git a/grpc/tests/src/test/contract_api/account/mod.rs b/grpc/tests/src/test/contract_api/account/mod.rs new file mode 100644 index 0000000000..0bdf371daa --- /dev/null +++ b/grpc/tests/src/test/contract_api/account/mod.rs @@ -0,0 +1,4 @@ +mod associated_keys; +mod authorized_keys; +mod key_management_thresholds; +mod named_keys; diff --git a/grpc/tests/src/test/contract_api/account/named_keys.rs b/grpc/tests/src/test/contract_api/account/named_keys.rs new file mode 100644 index 0000000000..edf5d3a637 --- /dev/null +++ b/grpc/tests/src/test/contract_api/account/named_keys.rs @@ -0,0 +1,110 @@ +use std::convert::TryFrom; + +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{bytesrepr::FromBytes, runtime_args, CLTyped, CLValue, Key, RuntimeArgs, U512}; + +const CONTRACT_NAMED_KEYS: &str = "named_keys.wasm"; +const EXPECTED_UREF_VALUE: u64 = 123_456_789u64; + +const KEY1: &str = "hello-world"; +const KEY2: &str = "big-value"; + +const COMMAND_CREATE_UREF1: &str = "create-uref1"; +const COMMAND_CREATE_UREF2: &str = "create-uref2"; +const COMMAND_REMOVE_UREF1: &str = "remove-uref1"; +const COMMAND_REMOVE_UREF2: &str = "remove-uref2"; +const COMMAND_TEST_READ_UREF1: &str = "test-read-uref1"; +const COMMAND_TEST_READ_UREF2: &str = "test-read-uref2"; +const COMMAND_INCREASE_UREF2: &str = "increase-uref2"; +const COMMAND_OVERWRITE_UREF2: &str = "overwrite-uref2"; +const ARG_COMMAND: &str = "command"; + +fn run_command(builder: &mut InMemoryWasmTestBuilder, command: &str) { + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_NAMED_KEYS, + runtime_args! { ARG_COMMAND => command }, + ) + .build(); + builder + .exec(exec_request) + .commit() + .expect_success() + .finish(); +} + +fn read_value(builder: &mut InMemoryWasmTestBuilder, key: Key) -> T { + CLValue::try_from(builder.query(None, key, &[]).expect("should have value")) + .expect("should have CLValue") + .into_t() + .expect("should convert successfully") +} + +#[ignore] +#[test] +fn should_run_named_keys_contract() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + run_command(&mut builder, COMMAND_CREATE_UREF1); + + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + assert!(account.named_keys().contains_key(KEY1)); + assert!(!account.named_keys().contains_key(KEY2)); + + run_command(&mut builder, COMMAND_CREATE_UREF2); + + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + let uref1 = *account.named_keys().get(KEY1).expect("should have key"); + let uref2 = *account.named_keys().get(KEY2).expect("should have key"); + let value1: String = read_value(&mut builder, uref1); + let value2: U512 = read_value(&mut builder, uref2); + assert_eq!(value1, "Hello, world!"); + assert_eq!(value2, U512::max_value()); + + run_command(&mut builder, COMMAND_TEST_READ_UREF1); + + run_command(&mut builder, COMMAND_REMOVE_UREF1); + + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + assert!(!account.named_keys().contains_key(KEY1)); + assert!(account.named_keys().contains_key(KEY2)); + + run_command(&mut builder, COMMAND_TEST_READ_UREF2); + + run_command(&mut builder, COMMAND_INCREASE_UREF2); + + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + let uref2 = *account.named_keys().get(KEY2).expect("should have key"); + let value2: U512 = read_value(&mut builder, uref2); + assert_eq!(value2, U512::zero()); + + run_command(&mut builder, COMMAND_OVERWRITE_UREF2); + + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + let uref2 = *account.named_keys().get(KEY2).expect("should have key"); + let value2: U512 = read_value(&mut builder, uref2); + assert_eq!(value2, U512::from(EXPECTED_UREF_VALUE)); + + run_command(&mut builder, COMMAND_REMOVE_UREF2); + + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + assert!(!account.named_keys().contains_key(KEY1)); + assert!(!account.named_keys().contains_key(KEY2)); +} diff --git a/grpc/tests/src/test/contract_api/create_purse.rs b/grpc/tests/src/test/contract_api/create_purse.rs new file mode 100644 index 0000000000..211e353d5e --- /dev/null +++ b/grpc/tests/src/test/contract_api/create_purse.rs @@ -0,0 +1,163 @@ +use lazy_static::lazy_static; + +use engine_test_support::{ + internal::{ + ExecuteRequestBuilder, WasmTestBuilder, DEFAULT_PAYMENT, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_shared::transform::Transform; +use types::{account::AccountHash, runtime_args, Key, RuntimeArgs, U512}; + +const CONTRACT_CREATE_PURSE_01: &str = "create_purse_01.wasm"; +const CONTRACT_TRANSFER_PURSE_TO_ACCOUNT: &str = "transfer_purse_to_account.wasm"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const TEST_PURSE_NAME: &str = "test_purse"; +const ARG_PURSE_NAME: &str = "purse_name"; + +lazy_static! { + static ref ACCOUNT_1_INITIAL_BALANCE: U512 = *DEFAULT_PAYMENT; +} + +fn get_purse_key_from_mint_transform(mint_transform: &Transform) -> Key { + let keys = if let Transform::AddKeys(keys) = mint_transform { + keys + } else { + panic!( + "Mint transform is expected to be an AddKeys variant instead got {:?}", + mint_transform + ); + }; + + // Exactly one new key which is the new purse created + assert_eq!(keys.len(), 1); + let (map_key, map_value) = keys.iter().next().unwrap(); + + // Decode uref name + assert!( + map_key.starts_with("uref-"), + format!( + "expected uref to start with uref- but the map contains {:?}", + keys + ) + ); + + let decoded_purse = base16::decode(&map_key[5..69]).expect("should decode base16"); + assert_eq!(decoded_purse.len(), 32); + + *map_value +} + +#[ignore] +#[test] +fn should_insert_mint_add_keys_transform() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { "target" => ACCOUNT_1_ADDR, "amount" => *ACCOUNT_1_INITIAL_BALANCE}, + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_CREATE_PURSE_01, + runtime_args! { ARG_PURSE_NAME => TEST_PURSE_NAME }, + ) + .build(); + + let mint_transform: &Transform = { + let result = WasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .expect_success() + .commit() + .finish(); + + let mint_contract_hash = result.builder().get_mint_contract_hash(); + &result.builder().get_transforms()[0][&mint_contract_hash.into()] + }; + + get_purse_key_from_mint_transform(mint_transform); // <-- assert equivalent +} + +#[ignore] +#[test] +fn should_insert_account_into_named_keys() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { "target" => ACCOUNT_1_ADDR, "amount" => *ACCOUNT_1_INITIAL_BALANCE}, + ) + .build(); + + let exec_request_2 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_CREATE_PURSE_01, + runtime_args! { ARG_PURSE_NAME => TEST_PURSE_NAME }, + ) + .build(); + + let mut builder = WasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + builder.exec(exec_request_2).expect_success().commit(); + + let account_1 = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should have account"); + + assert!( + account_1.named_keys().contains_key(TEST_PURSE_NAME), + "account_1 named_keys should include test purse" + ); +} + +#[ignore] +#[test] +fn should_create_usable_purse() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { "target" => ACCOUNT_1_ADDR, "amount" => *ACCOUNT_1_INITIAL_BALANCE}, + ) + .build(); + + let exec_request_2 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_CREATE_PURSE_01, + runtime_args! { ARG_PURSE_NAME => TEST_PURSE_NAME }, + ) + .build(); + let result = WasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .expect_success() + .commit() + .finish(); + + let account_1 = result + .builder() + .get_account(ACCOUNT_1_ADDR) + .expect("should have account"); + + let purse = account_1 + .named_keys() + .get(TEST_PURSE_NAME) + .expect("should have known key") + .into_uref() + .expect("should have uref"); + + let purse_balance = result.builder().get_purse_balance(purse); + assert!( + purse_balance.is_zero(), + "when created directly a purse has 0 balance" + ); +} diff --git a/grpc/tests/src/test/contract_api/get_arg.rs b/grpc/tests/src/test/contract_api/get_arg.rs new file mode 100644 index 0000000000..28d4b59d3d --- /dev/null +++ b/grpc/tests/src/test/contract_api/get_arg.rs @@ -0,0 +1,85 @@ +use engine_test_support::{ + internal::{ + utils, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{runtime_args, ApiError, RuntimeArgs, U512}; + +const CONTRACT_GET_ARG: &str = "get_arg.wasm"; +const ARG0_VALUE: &str = "Hello, world!"; +const ARG1_VALUE: u64 = 42; +const ARG_VALUE0: &str = "value0"; +const ARG_VALUE1: &str = "value1"; + +/// Calls get_arg contract and returns Ok(()) in case no error, or String which is the error message +/// returned by the engine +fn call_get_arg(args: RuntimeArgs) -> Result<(), String> { + let exec_request = + ExecuteRequestBuilder::standard(DEFAULT_ACCOUNT_ADDR, CONTRACT_GET_ARG, args).build(); + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + if !result.builder().is_error() { + return Ok(()); + } + + let response = result + .builder() + .get_exec_response(0) + .expect("should have a response"); + + let error_message = utils::get_error_message(response); + + Err(error_message) +} + +#[ignore] +#[test] +fn should_use_passed_argument() { + let args = runtime_args! { + ARG_VALUE0 => ARG0_VALUE, + ARG_VALUE1 => U512::from(ARG1_VALUE), + }; + call_get_arg(args).expect("Should successfuly call get_arg with 2 valid args"); +} + +#[ignore] +#[test] +fn should_revert_with_missing_arg() { + assert!(call_get_arg(RuntimeArgs::default()) + .expect_err("should fail") + .contains(&format!("{:?}", ApiError::MissingArgument),)); + assert!( + call_get_arg(runtime_args! { ARG_VALUE0 => String::from(ARG0_VALUE) }) + .expect_err("should fail") + .contains(&format!("{:?}", ApiError::MissingArgument)) + ); +} + +#[ignore] +#[test] +fn should_revert_with_invalid_argument() { + let res1 = + call_get_arg(runtime_args! {ARG_VALUE0 => U512::from(123)}).expect_err("should fail"); + assert!( + res1.contains(&format!("{:?}", ApiError::InvalidArgument,)), + "res1: {:?}", + res1 + ); + + let res2 = call_get_arg(runtime_args! { + ARG_VALUE0 => String::from(ARG0_VALUE), + ARG_VALUE1 => String::from("this is expected to be U512"), + }) + .expect_err("should fail"); + + assert!( + res2.contains(&format!("{:?}", ApiError::InvalidArgument,)), + "res2:{:?}", + res2 + ); +} diff --git a/grpc/tests/src/test/contract_api/get_blocktime.rs b/grpc/tests/src/test/contract_api/get_blocktime.rs new file mode 100644 index 0000000000..96394be19f --- /dev/null +++ b/grpc/tests/src/test/contract_api/get_blocktime.rs @@ -0,0 +1,27 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{runtime_args, RuntimeArgs}; + +const CONTRACT_GET_BLOCKTIME: &str = "get_blocktime.wasm"; +const ARG_KNOWN_BLOCK_TIME: &str = "known_block_time"; + +#[ignore] +#[test] +fn should_run_get_blocktime_contract() { + let block_time: u64 = 42; + + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GET_BLOCKTIME, + runtime_args! { ARG_KNOWN_BLOCK_TIME => block_time }, + ) + .with_block_time(block_time) + .build(); + InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .expect_success(); +} diff --git a/grpc/tests/src/test/contract_api/get_caller.rs b/grpc/tests/src/test/contract_api/get_caller.rs new file mode 100644 index 0000000000..dab679adf0 --- /dev/null +++ b/grpc/tests/src/test/contract_api/get_caller.rs @@ -0,0 +1,108 @@ +use engine_test_support::{ + internal::{ + ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{account::AccountHash, runtime_args, RuntimeArgs}; + +const CONTRACT_GET_CALLER: &str = "get_caller.wasm"; +const CONTRACT_GET_CALLER_SUBCALL: &str = "get_caller_subcall.wasm"; +const CONTRACT_TRANSFER_PURSE_TO_ACCOUNT: &str = "transfer_purse_to_account.wasm"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); + +#[ignore] +#[test] +fn should_run_get_caller_contract() { + InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec( + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GET_CALLER, + runtime_args! {"account" => DEFAULT_ACCOUNT_ADDR}, + ) + .build(), + ) + .expect_success() + .commit(); +} + +#[ignore] +#[test] +fn should_run_get_caller_contract_other_account() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder + .exec( + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! {"target" => ACCOUNT_1_ADDR, "amount"=> *DEFAULT_PAYMENT}, + ) + .build(), + ) + .expect_success() + .commit(); + + builder + .exec( + ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_GET_CALLER, + runtime_args! {"account" => ACCOUNT_1_ADDR}, + ) + .build(), + ) + .expect_success() + .commit(); +} + +#[ignore] +#[test] +fn should_run_get_caller_subcall_contract() { + { + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder + .exec( + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GET_CALLER_SUBCALL, + runtime_args! {"account" => DEFAULT_ACCOUNT_ADDR}, + ) + .build(), + ) + .expect_success() + .commit(); + } + + let mut builder = InMemoryWasmTestBuilder::default(); + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec( + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! {"target" => ACCOUNT_1_ADDR, "amount"=>*DEFAULT_PAYMENT}, + ) + .build(), + ) + .expect_success() + .commit(); + builder + .exec( + ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_GET_CALLER_SUBCALL, + runtime_args! {"account" => ACCOUNT_1_ADDR}, + ) + .build(), + ) + .expect_success() + .commit(); +} diff --git a/grpc/tests/src/test/contract_api/get_phase.rs b/grpc/tests/src/test/contract_api/get_phase.rs new file mode 100644 index 0000000000..a6b2d43c4b --- /dev/null +++ b/grpc/tests/src/test/contract_api/get_phase.rs @@ -0,0 +1,40 @@ +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{runtime_args, Phase, RuntimeArgs}; + +const ARG_PHASE: &str = "phase"; + +#[ignore] +#[test] +fn should_run_get_phase_contract() { + let default_account = DEFAULT_ACCOUNT_ADDR; + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_session_code( + "get_phase.wasm", + runtime_args! { ARG_PHASE => Phase::Session }, + ) + .with_payment_code( + "get_phase_payment.wasm", + runtime_args! { ARG_PHASE => Phase::Payment }, + ) + .with_authorization_keys(&[default_account]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .expect_success(); +} diff --git a/grpc/tests/src/test/contract_api/list_named_keys.rs b/grpc/tests/src/test/contract_api/list_named_keys.rs new file mode 100644 index 0000000000..9afb5387b6 --- /dev/null +++ b/grpc/tests/src/test/contract_api/list_named_keys.rs @@ -0,0 +1,44 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{account::AccountHash, contracts::NamedKeys, runtime_args, Key, RuntimeArgs}; + +const CONTRACT_LIST_NAMED_KEYS: &str = "list_named_keys.wasm"; +const NEW_NAME_ACCOUNT: &str = "Account"; +const NEW_NAME_HASH: &str = "Hash"; +const ARG_INITIAL_NAMED_KEYS: &str = "initial_named_args"; +const ARG_NEW_NAMED_KEYS: &str = "new_named_keys"; + +#[ignore] +#[test] +fn should_list_named_keys() { + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let initial_named_keys: NamedKeys = NamedKeys::new(); + + let new_named_keys = { + let account_hash = AccountHash::new([1; 32]); + let mut named_keys = NamedKeys::new(); + assert!(named_keys + .insert(NEW_NAME_ACCOUNT.to_string(), Key::Account(account_hash)) + .is_none()); + assert!(named_keys + .insert(NEW_NAME_HASH.to_string(), Key::Hash([2; 32])) + .is_none()); + named_keys + }; + + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_LIST_NAMED_KEYS, + runtime_args! { + ARG_INITIAL_NAMED_KEYS => initial_named_keys, + ARG_NEW_NAMED_KEYS => new_named_keys, + }, + ) + .build(); + + builder.exec(exec_request).commit().expect_success(); +} diff --git a/grpc/tests/src/test/contract_api/main_purse.rs b/grpc/tests/src/test/contract_api/main_purse.rs new file mode 100644 index 0000000000..0fdd10991f --- /dev/null +++ b/grpc/tests/src/test/contract_api/main_purse.rs @@ -0,0 +1,72 @@ +use engine_test_support::{ + internal::{ + ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_shared::stored_value::StoredValue; +use types::{account::AccountHash, runtime_args, Key, RuntimeArgs}; + +const CONTRACT_MAIN_PURSE: &str = "main_purse.wasm"; +const CONTRACT_TRANSFER_PURSE_TO_ACCOUNT: &str = "transfer_purse_to_account.wasm"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +#[ignore] +#[test] +fn should_run_main_purse_contract_default_account() { + let mut builder = InMemoryWasmTestBuilder::default(); + + let builder = builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let default_account = if let Ok(StoredValue::Account(account)) = + builder.query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + { + account + } else { + panic!("could not get account") + }; + + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_MAIN_PURSE, + runtime_args! { "purse" => default_account.main_purse() }, + ) + .build(); + + builder.exec(exec_request).expect_success().commit(); +} + +#[ignore] +#[test] +fn should_run_main_purse_contract_account_1() { + let mut builder = InMemoryWasmTestBuilder::default(); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *DEFAULT_PAYMENT }, + ) + .build(); + + let builder = builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit(); + + let account_1 = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should get account"); + + let exec_request_2 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_MAIN_PURSE, + runtime_args! { "purse" => account_1.main_purse() }, + ) + .build(); + + builder.exec(exec_request_2).expect_success().commit(); +} diff --git a/grpc/tests/src/test/contract_api/mint_purse.rs b/grpc/tests/src/test/contract_api/mint_purse.rs new file mode 100644 index 0000000000..c1768666cc --- /dev/null +++ b/grpc/tests/src/test/contract_api/mint_purse.rs @@ -0,0 +1,48 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, WasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; + +const CONTRACT_MINT_PURSE: &str = "mint_purse.wasm"; +const CONTRACT_TRANSFER_TO_ACCOUNT: &str = "transfer_to_account_u512.wasm"; +const SYSTEM_ADDR: AccountHash = AccountHash::new([0u8; 32]); +const TRANSFER_AMOUNT: u64 = 250_000_000 + 1000; + +#[ignore] +#[test] +fn should_run_mint_purse_contract() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { "target" =>SYSTEM_ADDR, "amount" => U512::from(TRANSFER_AMOUNT) }, + ) + .build(); + let exec_request_2 = + ExecuteRequestBuilder::standard(SYSTEM_ADDR, CONTRACT_MINT_PURSE, RuntimeArgs::default()) + .build(); + + let mut builder = WasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).commit().expect_success(); + builder.exec(exec_request_2).commit().expect_success(); +} + +#[ignore] +#[test] +fn should_not_allow_non_system_accounts_to_mint() { + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_MINT_PURSE, + RuntimeArgs::default(), + ) + .build(); + + assert!(WasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .is_error()); +} diff --git a/grpc/tests/src/test/contract_api/mod.rs b/grpc/tests/src/test/contract_api/mod.rs new file mode 100644 index 0000000000..902e1ec121 --- /dev/null +++ b/grpc/tests/src/test/contract_api/mod.rs @@ -0,0 +1,16 @@ +mod account; +mod create_purse; +mod get_arg; +mod get_blocktime; +mod get_caller; +mod get_phase; +mod list_named_keys; +mod main_purse; +mod mint_purse; +mod revert; +mod subcall; +mod transfer; +mod transfer_purse_to_account; +mod transfer_purse_to_purse; +mod transfer_stored; +mod transfer_u512_stored; diff --git a/grpc/tests/src/test/contract_api/revert.rs b/grpc/tests/src/test/contract_api/revert.rs new file mode 100644 index 0000000000..3cc13ccf92 --- /dev/null +++ b/grpc/tests/src/test/contract_api/revert.rs @@ -0,0 +1,20 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::RuntimeArgs; + +const REVERT_WASM: &str = "revert.wasm"; + +#[ignore] +#[test] +fn should_revert() { + let exec_request = + ExecuteRequestBuilder::standard(DEFAULT_ACCOUNT_ADDR, REVERT_WASM, RuntimeArgs::default()) + .build(); + InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .is_error(); +} diff --git a/grpc/tests/src/test/contract_api/subcall.rs b/grpc/tests/src/test/contract_api/subcall.rs new file mode 100644 index 0000000000..175e03c54c --- /dev/null +++ b/grpc/tests/src/test/contract_api/subcall.rs @@ -0,0 +1,262 @@ +use num_traits::cast::AsPrimitive; + +use engine_test_support::{ + internal::{ + ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::CONV_RATE; +use types::{contracts::CONTRACT_INITIAL_VERSION, runtime_args, RuntimeArgs, U512}; + +const ARG_TARGET: &str = "target_contract"; +const ARG_GAS_AMOUNT: &str = "gas_amount"; +const ARG_METHOD_NAME: &str = "method_name"; + +#[ignore] +#[test] +fn should_charge_gas_for_subcall() { + const CONTRACT_NAME: &str = "measure_gas_subcall.wasm"; + const DO_NOTHING: &str = "do-nothing"; + const DO_SOMETHING: &str = "do-something"; + const NO_SUBCALL: &str = "no-subcall"; + + let do_nothing_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_NAME, + runtime_args! { ARG_TARGET => DO_NOTHING }, + ) + .build(); + + let do_something_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_NAME, + runtime_args! { ARG_TARGET => DO_SOMETHING }, + ) + .build(); + + let no_subcall_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_NAME, + runtime_args! { ARG_TARGET => NO_SUBCALL }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(do_nothing_request).expect_success().commit(); + + builder.exec(do_something_request).expect_success().commit(); + + builder.exec(no_subcall_request).expect_success().commit(); + + let do_nothing_cost = builder.exec_costs(0)[0]; + + let do_something_cost = builder.exec_costs(1)[0]; + + let no_subcall_cost = builder.exec_costs(2)[0]; + + assert_ne!( + do_nothing_cost, do_something_cost, + "should have different costs" + ); + + assert_ne!( + no_subcall_cost, do_something_cost, + "should have different costs" + ); + + assert!( + do_nothing_cost < do_something_cost, + "should cost more to do something via subcall" + ); + + assert!( + no_subcall_cost < do_nothing_cost, + "do nothing in a subcall should cost more than no subcall" + ); +} + +#[ignore] +#[test] +fn should_add_all_gas_for_subcall() { + const CONTRACT_NAME: &str = "add_gas_subcall.wasm"; + const ADD_GAS_FROM_SESSION: &str = "add-gas-from-session"; + const ADD_GAS_VIA_SUBCALL: &str = "add-gas-via-subcall"; + + // Use 90% of the standard test contract's balance + let gas_to_add: U512 = *DEFAULT_PAYMENT / CONV_RATE * 9 / 10; + + assert!(gas_to_add <= U512::from(i32::max_value())); + let gas_to_add_as_arg: i32 = gas_to_add.as_(); + + let add_zero_gas_from_session_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_NAME, + runtime_args! { + ARG_GAS_AMOUNT => 0, + ARG_METHOD_NAME => ADD_GAS_FROM_SESSION, + }, + ) + .build(); + + let add_some_gas_from_session_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_NAME, + runtime_args! { + ARG_GAS_AMOUNT => gas_to_add_as_arg, + ARG_METHOD_NAME => ADD_GAS_FROM_SESSION, + }, + ) + .build(); + + let add_zero_gas_via_subcall_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_NAME, + runtime_args! { + ARG_GAS_AMOUNT => 0, + ARG_METHOD_NAME => ADD_GAS_VIA_SUBCALL, + }, + ) + .build(); + + let add_some_gas_via_subcall_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_NAME, + runtime_args! { + ARG_GAS_AMOUNT => gas_to_add_as_arg, + ARG_METHOD_NAME => ADD_GAS_VIA_SUBCALL, + }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder + .exec(add_zero_gas_from_session_request) + .expect_success() + .commit(); + builder + .exec(add_some_gas_from_session_request) + .expect_success() + .commit(); + builder + .exec(add_zero_gas_via_subcall_request) + .expect_success() + .commit(); + builder + .exec(add_some_gas_via_subcall_request) + .expect_success() + .commit() + .finish(); + + let add_zero_gas_from_session_cost = builder.exec_costs(0)[0]; + let add_some_gas_from_session_cost = builder.exec_costs(1)[0]; + let add_zero_gas_via_subcall_cost = builder.exec_costs(2)[0]; + let add_some_gas_via_subcall_cost = builder.exec_costs(3)[0]; + + assert!(add_zero_gas_from_session_cost.value() < gas_to_add); + assert!(add_some_gas_from_session_cost.value() > gas_to_add); + assert_eq!( + add_some_gas_from_session_cost.value(), + gas_to_add + add_zero_gas_from_session_cost.value() + ); + + assert!(add_zero_gas_via_subcall_cost.value() < gas_to_add); + assert!(add_some_gas_via_subcall_cost.value() > gas_to_add); + assert_eq!( + add_some_gas_via_subcall_cost.value(), + gas_to_add + add_zero_gas_via_subcall_cost.value() + ); +} + +#[ignore] +#[test] +fn expensive_subcall_should_cost_more() { + const DO_NOTHING: &str = "do_nothing_stored.wasm"; + const EXPENSIVE_CALCULATION: &str = "expensive_calculation.wasm"; + const DO_NOTHING_PACKAGE_HASH_KEY_NAME: &str = "do_nothing_package_hash"; + const EXPENSIVE_CALCULATION_KEY: &str = "expensive-calculation"; + const ENTRY_FUNCTION_NAME: &str = "delegate"; + + let store_do_nothing_request = + ExecuteRequestBuilder::standard(DEFAULT_ACCOUNT_ADDR, DO_NOTHING, RuntimeArgs::default()) + .build(); + + let store_calculation_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + EXPENSIVE_CALCULATION, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + // store the contracts first + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder + .exec(store_do_nothing_request) + .expect_success() + .commit(); + + builder + .exec(store_calculation_request) + .expect_success() + .commit() + .finish(); + + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get account"); + + let expensive_calculation_contract_hash = account + .named_keys() + .get(EXPENSIVE_CALCULATION_KEY) + .expect("should get expensive_calculation contract hash") + .into_hash() + .expect("should get hash"); + + // execute the contracts via subcalls + + let call_do_nothing_request = ExecuteRequestBuilder::versioned_contract_call_by_hash_key_name( + DEFAULT_ACCOUNT_ADDR, + DO_NOTHING_PACKAGE_HASH_KEY_NAME, + Some(CONTRACT_INITIAL_VERSION), + ENTRY_FUNCTION_NAME, + RuntimeArgs::new(), + ) + .build(); + + let call_expensive_calculation_request = ExecuteRequestBuilder::contract_call_by_hash( + DEFAULT_ACCOUNT_ADDR, + expensive_calculation_contract_hash, + "calculate", + RuntimeArgs::default(), + ) + .build(); + + builder + .exec(call_do_nothing_request) + .expect_success() + .commit(); + + builder + .exec(call_expensive_calculation_request) + .expect_success() + .commit(); + + let do_nothing_cost = builder.exec_costs(2)[0]; + + let expensive_calculation_cost = builder.exec_costs(3)[0]; + + assert!( + do_nothing_cost < expensive_calculation_cost, + "calculation cost should be higher than doing nothing cost" + ); +} diff --git a/grpc/tests/src/test/contract_api/transfer.rs b/grpc/tests/src/test/contract_api/transfer.rs new file mode 100644 index 0000000000..5bf2256a7a --- /dev/null +++ b/grpc/tests/src/test/contract_api/transfer.rs @@ -0,0 +1,354 @@ +use lazy_static::lazy_static; + +use engine_test_support::{ + internal::{ + utils, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, +}; +use node::contract_core::engine_state::CONV_RATE; +use node::contract_shared::motes::Motes; +use types::{account::AccountHash, runtime_args, ApiError, RuntimeArgs, U512}; + +const CONTRACT_TRANSFER_PURSE_TO_ACCOUNT: &str = "transfer_purse_to_account.wasm"; +const CONTRACT_TRANSFER_TO_ACCOUNT: &str = "transfer_to_account_u512.wasm"; + +lazy_static! { + static ref TRANSFER_1_AMOUNT: U512 = U512::from(250_000_000) + 1000; + static ref TRANSFER_2_AMOUNT: U512 = U512::from(750); + static ref TRANSFER_2_AMOUNT_WITH_ADV: U512 = *DEFAULT_PAYMENT + *TRANSFER_2_AMOUNT; + static ref TRANSFER_TOO_MUCH: U512 = U512::from(u64::max_value()); + static ref ACCOUNT_1_INITIAL_BALANCE: U512 = *DEFAULT_PAYMENT; +} + +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const ACCOUNT_2_ADDR: AccountHash = AccountHash::new([2u8; 32]); +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +#[ignore] +#[test] +fn should_transfer_to_account() { + let initial_genesis_amount: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + let transfer_amount: U512 = *TRANSFER_1_AMOUNT; + + // Run genesis + let mut builder = InMemoryWasmTestBuilder::default(); + + let builder = builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get account"); + + let default_account_purse = default_account.main_purse(); + + // Check genesis account balance + let genesis_balance = builder.get_purse_balance(default_account_purse); + + assert_eq!(genesis_balance, initial_genesis_amount,); + + // Exec transfer contract + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *TRANSFER_1_AMOUNT }, + ) + .build(); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should get account"); + let account_purse = account.main_purse(); + + // Check genesis account balance + + let genesis_balance = builder.get_purse_balance(default_account_purse); + + let gas_cost = + Motes::from_gas(builder.exec_costs(0)[0], CONV_RATE).expect("should convert gas to motes"); + + assert_eq!( + genesis_balance, + initial_genesis_amount - gas_cost.value() - transfer_amount + ); + + // Check account 1 balance + + let account_1_balance = builder.get_purse_balance(account_purse); + + assert_eq!(account_1_balance, transfer_amount,); +} + +#[ignore] +#[test] +fn should_transfer_from_account_to_account() { + let initial_genesis_amount: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + let transfer_1_amount: U512 = *TRANSFER_1_AMOUNT; + let transfer_2_amount: U512 = *TRANSFER_2_AMOUNT; + + // Run genesis + let mut builder = InMemoryWasmTestBuilder::default(); + + let builder = builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get account"); + + let default_account_purse = default_account.main_purse(); + + // Check genesis account balance + let genesis_balance = builder.get_purse_balance(default_account_purse); + + assert_eq!(genesis_balance, initial_genesis_amount,); + + // Exec transfer 1 contract + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *TRANSFER_1_AMOUNT }, + ) + .build(); + + builder.exec(exec_request_1).expect_success().commit(); + + let exec_1_response = builder + .get_exec_response(0) + .expect("should have exec response"); + + let genesis_balance = builder.get_purse_balance(default_account_purse); + + let gas_cost = Motes::from_gas(utils::get_exec_costs(exec_1_response)[0], CONV_RATE) + .expect("should convert"); + + assert_eq!( + genesis_balance, + initial_genesis_amount - gas_cost.value() - transfer_1_amount + ); + + // Check account 1 balance + let account_1 = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should have account 1"); + let account_1_purse = account_1.main_purse(); + let account_1_balance = builder.get_purse_balance(account_1_purse); + + assert_eq!(account_1_balance, transfer_1_amount,); + + // Exec transfer 2 contract + + let exec_request_2 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_2_ADDR, ARG_AMOUNT => *TRANSFER_2_AMOUNT }, + ) + .build(); + + builder.exec(exec_request_2).expect_success().commit(); + + let exec_2_response = builder + .get_exec_response(1) + .expect("should have exec response"); + + let account_2 = builder + .get_account(ACCOUNT_2_ADDR) + .expect("should have account 2"); + + let account_2_purse = account_2.main_purse(); + + // Check account 1 balance + + let account_1_balance = builder.get_purse_balance(account_1_purse); + + let gas_cost = Motes::from_gas(utils::get_exec_costs(exec_2_response)[0], CONV_RATE) + .expect("should convert"); + + assert_eq!( + account_1_balance, + transfer_1_amount - gas_cost.value() - transfer_2_amount + ); + + let account_2_balance = builder.get_purse_balance(account_2_purse); + + assert_eq!(account_2_balance, transfer_2_amount,); +} + +#[ignore] +#[test] +fn should_transfer_to_existing_account() { + let initial_genesis_amount: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + let transfer_1_amount: U512 = *TRANSFER_1_AMOUNT; + let transfer_2_amount: U512 = *TRANSFER_2_AMOUNT; + + // Run genesis + let mut builder = InMemoryWasmTestBuilder::default(); + + let builder = builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get account"); + + let default_account_purse = default_account.main_purse(); + + // Check genesis account balance + let genesis_balance = builder.get_purse_balance(default_account_purse); + + assert_eq!(genesis_balance, initial_genesis_amount,); + + // Exec transfer 1 contract + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *TRANSFER_1_AMOUNT }, + ) + .build(); + + builder.exec(exec_request_1).expect_success().commit(); + + // Exec transfer contract + + let account_1 = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should get account"); + + let account_1_purse = account_1.main_purse(); + + // Check genesis account balance + + let genesis_balance = builder.get_purse_balance(default_account_purse); + + let gas_cost = + Motes::from_gas(builder.exec_costs(0)[0], CONV_RATE).expect("should convert gas to motes"); + + assert_eq!( + genesis_balance, + initial_genesis_amount - gas_cost.value() - transfer_1_amount + ); + + // Check account 1 balance + + let account_1_balance = builder.get_purse_balance(account_1_purse); + + assert_eq!(account_1_balance, transfer_1_amount,); + + // Exec transfer contract + + let exec_request_2 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_2_ADDR, ARG_AMOUNT => *TRANSFER_2_AMOUNT }, + ) + .build(); + builder.exec(exec_request_2).expect_success().commit(); + + let account_2 = builder + .get_account(ACCOUNT_2_ADDR) + .expect("should get account"); + + let account_2_purse = account_2.main_purse(); + + // Check account 1 balance + + let account_1_balance = builder.get_purse_balance(account_1_purse); + + let gas_cost = + Motes::from_gas(builder.exec_costs(1)[0], CONV_RATE).expect("should convert gas to motes"); + + assert_eq!( + account_1_balance, + transfer_1_amount - gas_cost.value() - transfer_2_amount, + ); + + // Check account 2 balance + + let account_2_balance_transform = builder.get_purse_balance(account_2_purse); + + assert_eq!(account_2_balance_transform, transfer_2_amount); +} + +#[ignore] +#[test] +fn should_fail_when_insufficient_funds() { + // Run genesis + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *TRANSFER_1_AMOUNT }, + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_2_ADDR, ARG_AMOUNT => *TRANSFER_2_AMOUNT_WITH_ADV }, + ) + .build(); + + let exec_request_3 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_2_ADDR, ARG_AMOUNT => *TRANSFER_TOO_MUCH }, + ) + .build(); + + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + // Exec transfer contract + .exec(exec_request_1) + .expect_success() + .commit() + // Exec transfer contract + .exec(exec_request_2) + .expect_success() + .commit() + // // Exec transfer contract + .exec(exec_request_3) + .commit() + .finish(); + + let error_msg = result + .builder() + .exec_error_message(2) + .expect("should have error message"); + assert!( + error_msg.contains(&format!("{:?}", ApiError::Transfer)), + error_msg + ); +} + +#[ignore] +#[test] +fn should_transfer_total_amount() { + let mut builder = InMemoryWasmTestBuilder::default(); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { "target" => ACCOUNT_1_ADDR, "amount" => *ACCOUNT_1_INITIAL_BALANCE }, + ) + .build(); + + let exec_request_2 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { "target" => ACCOUNT_2_ADDR, "amount" => *ACCOUNT_1_INITIAL_BALANCE }, + ) + .build(); + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .commit() + .expect_success() + .finish(); +} diff --git a/grpc/tests/src/test/contract_api/transfer_purse_to_account.rs b/grpc/tests/src/test/contract_api/transfer_purse_to_account.rs new file mode 100644 index 0000000000..001be34468 --- /dev/null +++ b/grpc/tests/src/test/contract_api/transfer_purse_to_account.rs @@ -0,0 +1,255 @@ +use std::convert::TryFrom; + +use lazy_static::lazy_static; + +use engine_test_support::{ + internal::{ + ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, +}; +use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use types::{ + account::AccountHash, runtime_args, ApiError, CLValue, Key, RuntimeArgs, TransferResult, + TransferredTo, U512, +}; + +const CONTRACT_TRANSFER_PURSE_TO_ACCOUNT: &str = "transfer_purse_to_account.wasm"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([42u8; 32]); +lazy_static! { + static ref ACCOUNT_1_INITIAL_FUND: U512 = *DEFAULT_PAYMENT + 42; +} + +#[ignore] +#[test] +fn should_run_purse_to_account_transfer() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let genesis_account_hash = DEFAULT_ACCOUNT_ADDR; + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { "target" => account_1_account_hash, "amount" => *ACCOUNT_1_INITIAL_FUND }, + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + account_1_account_hash, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { "target" => genesis_account_hash, "amount" => U512::from(1) }, + ) + .build(); + let mut builder = InMemoryWasmTestBuilder::default(); + + // + // Exec 1 - New account [42; 32] is created + // + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit(); + + // Get transforms output for genesis account + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get genesis account"); + + // Obtain main purse's balance + let final_balance_key = default_account.named_keys()["final_balance"].normalize(); + let final_balance = CLValue::try_from( + builder + .query(None, final_balance_key, &[]) + .expect("should have final balance"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be U512"); + assert_eq!( + final_balance, + U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE) - (*DEFAULT_PAYMENT * 2) - 42 + ); + + // Get the `transfer_result` for a given account + let transfer_result_key = default_account.named_keys()["transfer_result"].normalize(); + let transfer_result = CLValue::try_from( + builder + .query(None, transfer_result_key, &[]) + .expect("should have transfer result"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be String"); + // Main assertion for the result of `transfer_from_purse_to_purse` + assert_eq!( + transfer_result, + format!("{:?}", TransferResult::Ok(TransferredTo::NewAccount)) + ); + + // Inspect AddKeys for that new account to find it's purse id + let new_account = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should have new account"); + + let new_purse = new_account.main_purse(); + // This is the new lookup key that will be present in AddKeys for a mint + // contract uref + let new_purse_key = new_purse.remove_access_rights().as_string(); + + // Obtain transforms for a mint account + let mint_contract_hash = builder.get_mint_contract_hash(); + + let mint_contract = builder + .get_contract(mint_contract_hash) + .expect("should have mint contract"); + + assert!(mint_contract.named_keys().contains_key(&new_purse_key)); + + // Find new account's purse uref + let new_account_purse_uref = &mint_contract.named_keys()[&new_purse_key]; + let purse_secondary_balance = CLValue::try_from( + builder + .query(None, new_account_purse_uref.normalize(), &[]) + .expect("should have final balance"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be U512"); + assert_eq!(purse_secondary_balance, *ACCOUNT_1_INITIAL_FUND); + + // Exec 2 - Transfer from new account back to genesis to verify + // TransferToExisting + + builder + .exec(exec_request_2) + .expect_success() + .commit() + .finish(); + + let account_1 = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should get account 1"); + + // Obtain main purse's balance + let final_balance_key = account_1.named_keys()["final_balance"].normalize(); + let final_balance = CLValue::try_from( + builder + .query(None, final_balance_key, &[]) + .expect("should have final balance"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be U512"); + assert_eq!(final_balance, U512::from(41)); + + // Get the `transfer_result` for a given account + let transfer_result_key = account_1.named_keys()["transfer_result"].normalize(); + let transfer_result = CLValue::try_from( + builder + .query(None, transfer_result_key, &[]) + .expect("should have transfer result"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be String"); + // Main assertion for the result of `transfer_from_purse_to_purse` + assert_eq!( + transfer_result, + format!("{:?}", TransferResult::Ok(TransferredTo::ExistingAccount)) + ); + + // Get transforms output for genesis + let transforms = builder.get_transforms(); + let transform = &transforms[1]; + let genesis_transforms = transform + .get(&Key::Account(DEFAULT_ACCOUNT_ADDR)) + .expect("Unable to find transforms for a genesis account"); + + // Genesis account is unchanged + assert_eq!(genesis_transforms, &Transform::Identity); + + let genesis_transforms = builder.get_genesis_transforms(); + + let balance_uref = genesis_transforms + .iter() + .find_map(|(k, t)| match (k, t) { + (uref @ Key::URef(_), Transform::Write(StoredValue::CLValue(cl_value))) => + // 100_000_000_000i64 is the initial balance of genesis + { + if cl_value.to_owned().into_t::().unwrap_or_default() + == U512::from(100_000_000_000i64) + { + Some(*uref) + } else { + None + } + } + _ => None, + }) + .expect("Could not find genesis account balance uref"); + + let updated_balance = &transform[&balance_uref.normalize()]; + assert_eq!(updated_balance, &Transform::AddUInt512(U512::from(1))); +} + +#[ignore] +#[test] +fn should_fail_when_sending_too_much_from_purse_to_account() { + let account_1_key = ACCOUNT_1_ADDR; + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { "target" => account_1_key, "amount" => U512::max_value() }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .finish(); + + // Get transforms output for genesis account + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get genesis account"); + + // Obtain main purse's balance + let final_balance_key = default_account.named_keys()["final_balance"].normalize(); + let final_balance = CLValue::try_from( + builder + .query(None, final_balance_key, &[]) + .expect("should have final balance"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be U512"); + // When trying to send too much coins the balance is left unchanged + assert_eq!( + final_balance, + U512::from(100_000_000_000u64) - *DEFAULT_PAYMENT, + "final balance incorrect" + ); + + // Get the `transfer_result` for a given account + let transfer_result_key = default_account.named_keys()["transfer_result"].normalize(); + let transfer_result = CLValue::try_from( + builder + .query(None, transfer_result_key, &[]) + .expect("should have transfer result"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be String"); + + // Main assertion for the result of `transfer_from_purse_to_purse` + assert_eq!( + transfer_result, + format!("{:?}", Result::<(), _>::Err(ApiError::Transfer)), + "Transfer Error incorrect" + ); +} diff --git a/grpc/tests/src/test/contract_api/transfer_purse_to_purse.rs b/grpc/tests/src/test/contract_api/transfer_purse_to_purse.rs new file mode 100644 index 0000000000..f9f310041d --- /dev/null +++ b/grpc/tests/src/test/contract_api/transfer_purse_to_purse.rs @@ -0,0 +1,201 @@ +use std::convert::TryFrom; + +use types::{runtime_args, ApiError, CLValue, Key, RuntimeArgs, U512}; + +use engine_test_support::{ + internal::{ + ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, +}; + +const CONTRACT_TRANSFER_PURSE_TO_PURSE: &str = "transfer_purse_to_purse.wasm"; +const PURSE_TO_PURSE_AMOUNT: u64 = 42; +const ARG_SOURCE: &str = "source"; +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +#[ignore] +#[test] +fn should_run_purse_to_purse_transfer() { + let source = "purse:main".to_string(); + let target = "purse:secondary".to_string(); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_PURSE, + runtime_args! { + ARG_SOURCE => source, + ARG_TARGET => target, + ARG_AMOUNT => U512::from(PURSE_TO_PURSE_AMOUNT) + }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .finish(); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get genesis account"); + + // Get the `purse_transfer_result` for a given + let purse_transfer_result_key = + default_account.named_keys()["purse_transfer_result"].normalize(); + let purse_transfer_result = CLValue::try_from( + builder + .query(None, purse_transfer_result_key, &[]) + .expect("should have purse transfer result"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be String"); + // Main assertion for the result of `transfer_from_purse_to_purse` + assert_eq!( + purse_transfer_result, + format!("{:?}", Result::<_, ApiError>::Ok(()),) + ); + + let main_purse_balance_key = default_account.named_keys()["main_purse_balance"].normalize(); + let main_purse_balance = CLValue::try_from( + builder + .query(None, main_purse_balance_key, &[]) + .expect("should have main purse balance"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be U512"); + + // Assert secondary purse value after successful transfer + let purse_secondary_key = default_account.named_keys()["purse:secondary"]; + let _purse_main_key = default_account.named_keys()["purse:main"]; + + // Lookup key used to find the actual purse uref + // TODO: This should be more consistent + let purse_secondary_lookup_key = purse_secondary_key + .as_uref() + .unwrap() + .remove_access_rights() + .as_string(); + + let mint_contract_hash = builder.get_mint_contract_hash(); + let mint_contract = builder + .get_contract(mint_contract_hash) + .expect("should have mint contract"); + + // Find `purse:secondary`. + let purse_secondary_uref = mint_contract.named_keys()[&purse_secondary_lookup_key]; + let purse_secondary_key: Key = purse_secondary_uref.normalize(); + let purse_secondary_balance = CLValue::try_from( + builder + .query(None, purse_secondary_key, &[]) + .expect("should have main purse balance"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be U512"); + + // Final balance of the destination purse + assert_eq!(purse_secondary_balance, U512::from(PURSE_TO_PURSE_AMOUNT)); + assert_eq!( + main_purse_balance, + U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE) - *DEFAULT_PAYMENT - PURSE_TO_PURSE_AMOUNT + ); +} + +#[ignore] +#[test] +fn should_run_purse_to_purse_transfer_with_error() { + // This test runs a contract that's after every call extends the same key with + // more data + let source = "purse:main".to_string(); + let target = "purse:secondary".to_string(); + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_PURSE, + runtime_args! { ARG_SOURCE => source, ARG_TARGET => target, ARG_AMOUNT => U512::from(999_999_999_999i64) }, + ) + .build(); + let mut builder = InMemoryWasmTestBuilder::default(); + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .finish(); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get genesis account"); + + // Get the `purse_transfer_result` for a given + let purse_transfer_result_key = + default_account.named_keys()["purse_transfer_result"].normalize(); + let purse_transfer_result = CLValue::try_from( + builder + .query(None, purse_transfer_result_key, &[]) + .expect("should have purse transfer result"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be String"); + // Main assertion for the result of `transfer_from_purse_to_purse` + assert_eq!( + purse_transfer_result, + format!("{:?}", Result::<(), _>::Err(ApiError::Transfer)), + ); + + // Obtain main purse's balance + let main_purse_balance_key = default_account.named_keys()["main_purse_balance"].normalize(); + let main_purse_balance = CLValue::try_from( + builder + .query(None, main_purse_balance_key, &[]) + .expect("should have main purse balance"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be U512"); + + // Assert secondary purse value after successful transfer + let purse_secondary_key = default_account.named_keys()["purse:secondary"]; + let _purse_main_key = default_account.named_keys()["purse:main"]; + + // Lookup key used to find the actual purse uref + // TODO: This should be more consistent + let purse_secondary_lookup_key = purse_secondary_key + .as_uref() + .unwrap() + .remove_access_rights() + .as_string(); + + let mint_contract_uref = builder.get_mint_contract_hash(); + let mint_contract = builder + .get_contract(mint_contract_uref) + .expect("should have mint contract"); + + // Find `purse:secondary` for a balance + let purse_secondary_uref = mint_contract.named_keys()[&purse_secondary_lookup_key]; + let purse_secondary_key: Key = purse_secondary_uref.normalize(); + let purse_secondary_balance = CLValue::try_from( + builder + .query(None, purse_secondary_key, &[]) + .expect("should have main purse balance"), + ) + .expect("should be a CLValue") + .into_t::() + .expect("should be U512"); + + // Final balance of the destination purse equals to 0 as this purse is created + // as new. + assert_eq!(purse_secondary_balance, U512::from(0)); + assert_eq!( + main_purse_balance, + U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE) - *DEFAULT_PAYMENT + ); +} diff --git a/grpc/tests/src/test/contract_api/transfer_stored.rs b/grpc/tests/src/test/contract_api/transfer_stored.rs new file mode 100644 index 0000000000..9f8bfdaecf --- /dev/null +++ b/grpc/tests/src/test/contract_api/transfer_stored.rs @@ -0,0 +1,108 @@ +use engine_test_support::{ + internal::{ + utils, DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, + DEFAULT_ACCOUNT_KEY, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, +}; +use node::contract_core::engine_state::CONV_RATE; +use node::contract_shared::motes::Motes; +use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; + +const CONTRACT_TRANSFER_TO_ACCOUNT_NAME: &str = "transfer_to_account"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); + +#[ignore] +#[test] +fn should_transfer_to_account_stored() { + let mut builder = InMemoryWasmTestBuilder::default(); + { + // first, store transfer contract + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &format!("{}_stored.wasm", CONTRACT_TRANSFER_TO_ACCOUNT_NAME), + RuntimeArgs::default(), + ) + .build(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + builder.exec_commit_finish(exec_request); + } + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + let contract_hash = default_account + .named_keys() + .get(CONTRACT_TRANSFER_TO_ACCOUNT_NAME) + .expect("contract_hash should exist") + .into_hash() + .expect("should be a hash"); + + let response = builder + .get_exec_response(0) + .expect("there should be a response") + .clone(); + let mut result = utils::get_success_result(&response); + let gas = result.cost(); + let motes_alpha = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + + let modified_balance_alpha: U512 = builder.get_purse_balance(default_account.main_purse()); + + let transferred_amount: u64 = 1; + let payment_purse_amount = 10_000_000; + + // next make another deploy that USES stored payment logic + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_hash( + contract_hash, + "transfer", + runtime_args! { "target" => ACCOUNT_1_ADDR, "amount" => transferred_amount }, + ) + .with_empty_payment_bytes(runtime_args! { + "amount" => U512::from(payment_purse_amount), + }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec_commit_finish(exec_request); + + let modified_balance_bravo: U512 = builder.get_purse_balance(default_account.main_purse()); + + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + + let response = builder + .get_exec_response(1) + .expect("there should be a response") + .clone(); + + result = utils::get_success_result(&response); + let gas = result.cost(); + let motes_bravo = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + + let tally = motes_alpha.value() + + motes_bravo.value() + + U512::from(transferred_amount) + + modified_balance_bravo; + + assert!( + modified_balance_alpha < initial_balance, + "balance should be less than initial balance" + ); + + assert!( + modified_balance_bravo < modified_balance_alpha, + "second modified balance should be less than first modified balance" + ); + + assert_eq!( + initial_balance, tally, + "no net resources should be gained or lost post-distribution" + ); +} diff --git a/grpc/tests/src/test/contract_api/transfer_u512_stored.rs b/grpc/tests/src/test/contract_api/transfer_u512_stored.rs new file mode 100644 index 0000000000..70c433a4e6 --- /dev/null +++ b/grpc/tests/src/test/contract_api/transfer_u512_stored.rs @@ -0,0 +1,109 @@ +use engine_test_support::{ + internal::{ + utils, DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, + DEFAULT_ACCOUNT_KEY, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, +}; +use node::contract_core::engine_state::CONV_RATE; +use node::contract_shared::motes::Motes; +use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; + +const FUNCTION_NAME: &str = "transfer"; +const CONTRACT_KEY_NAME: &str = "transfer_to_account"; +const CONTRACT_TRANSFER_TO_ACCOUNT_NAME: &str = "transfer_to_account_u512"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const TRANSFER_AMOUNT: u64 = 1; + +#[ignore] +#[test] +fn should_transfer_to_account_stored() { + let mut builder = InMemoryWasmTestBuilder::default(); + { + // first, store transfer contract + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &format!("{}_stored.wasm", CONTRACT_TRANSFER_TO_ACCOUNT_NAME), + RuntimeArgs::default(), + ) + .build(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + builder.exec_commit_finish(exec_request); + } + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + let contract_hash = default_account + .named_keys() + .get(CONTRACT_KEY_NAME) + .expect("contract_hash should exist") + .into_hash() + .expect("should be a hash"); + + let response = builder + .get_exec_response(0) + .expect("there should be a response") + .clone(); + let mut result = utils::get_success_result(&response); + let gas = result.cost(); + let motes_alpha = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + + let modified_balance_alpha: U512 = builder.get_purse_balance(default_account.main_purse()); + + let transferred_amount: U512 = U512::from(TRANSFER_AMOUNT); + let payment_purse_amount = 10_000_000; + + // next make another deploy that USES stored payment logic + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_hash( + contract_hash, + FUNCTION_NAME, + runtime_args! { "target" => ACCOUNT_1_ADDR, "amount" => transferred_amount }, + ) + .with_empty_payment_bytes(runtime_args! { + "amount" => U512::from(payment_purse_amount), + }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec_commit_finish(exec_request); + + let modified_balance_bravo: U512 = builder.get_purse_balance(default_account.main_purse()); + + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + + let response = builder + .get_exec_response(1) + .expect("there should be a response") + .clone(); + + result = utils::get_success_result(&response); + let gas = result.cost(); + let motes_bravo = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + + let tally = + motes_alpha.value() + motes_bravo.value() + transferred_amount + modified_balance_bravo; + + assert!( + modified_balance_alpha < initial_balance, + "balance should be less than initial balance" + ); + + assert!( + modified_balance_bravo < modified_balance_alpha, + "second modified balance should be less than first modified balance" + ); + + assert_eq!( + initial_balance, tally, + "no net resources should be gained or lost post-distribution" + ); +} diff --git a/grpc/tests/src/test/contract_context.rs b/grpc/tests/src/test/contract_context.rs new file mode 100644 index 0000000000..168e6e7b11 --- /dev/null +++ b/grpc/tests/src/test/contract_context.rs @@ -0,0 +1,377 @@ +use assert_matches::assert_matches; +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::{engine_state::Error, execution}; +use types::{contracts::CONTRACT_INITIAL_VERSION, runtime_args, Key, RuntimeArgs}; + +const CONTRACT_HEADERS: &str = "contract_context.wasm"; +const PACKAGE_HASH_KEY: &str = "package_hash_key"; +const PACKAGE_ACCESS_KEY: &str = "package_access_key"; +const CONTRACT_HASH_KEY: &str = "contract_hash_key"; +const SESSION_CODE_TEST: &str = "session_code_test"; +const CONTRACT_CODE_TEST: &str = "contract_code_test"; +const ADD_NEW_KEY_AS_SESSION: &str = "add_new_key_as_session"; +const NEW_KEY: &str = "new_key"; +const SESSION_CODE_CALLER_AS_CONTRACT: &str = "session_code_caller_as_contract"; +const ARG_AMOUNT: &str = "amount"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[ignore] +#[test] +fn should_enforce_intended_execution_contexts() { + // This test runs a contract that extends the same key with more data after every call. + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_HEADERS, + RuntimeArgs::default(), + ) + .build(); + + let exec_request_2 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + SESSION_CODE_TEST, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let exec_request_3 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + CONTRACT_CODE_TEST, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let exec_request_4 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + ADD_NEW_KEY_AS_SESSION, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([4; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + builder.exec(exec_request_2).expect_success().commit(); + + builder.exec(exec_request_3).expect_success().commit(); + + builder.exec(exec_request_4).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let _package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let _new_key = account + .named_keys() + .get(NEW_KEY) + .expect("new key should be there"); + + // Check version + + let contract_version_stored = builder + .query( + None, + Key::Account(DEFAULT_ACCOUNT_ADDR), + &[CONTRACT_VERSION], + ) + .expect("should query account") + .as_cl_value() + .cloned() + .expect("should be cl value"); + assert_eq!(contract_version_stored.into_t::().unwrap(), 1u32); +} + +#[ignore] +#[test] +fn should_enforce_intended_execution_context_direct_by_name() { + // This test runs a contract that extends the same key with more data after every call. + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_HEADERS, + RuntimeArgs::default(), + ) + .build(); + + let exec_request_2 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_named_key(CONTRACT_HASH_KEY, SESSION_CODE_TEST, args) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let exec_request_3 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_named_key(CONTRACT_HASH_KEY, CONTRACT_CODE_TEST, args) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let exec_request_4 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_named_key(CONTRACT_HASH_KEY, ADD_NEW_KEY_AS_SESSION, args) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([4; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + builder.exec(exec_request_2).expect_success().commit(); + + builder.exec(exec_request_3).expect_success().commit(); + + builder.exec(exec_request_4).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let _package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let _new_key = account + .named_keys() + .get(NEW_KEY) + .expect("new key should be there"); +} + +#[ignore] +#[test] +fn should_enforce_intended_execution_context_direct_by_hash() { + // This test runs a contract that extends the same key with more data after every call. + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_HEADERS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let contract_hash = account + .named_keys() + .get(CONTRACT_HASH_KEY) + .expect("should have contract hash") + .into_hash() + .expect("should have hash"); + + let exec_request_2 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_hash(contract_hash, SESSION_CODE_TEST, args) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let exec_request_3 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_hash(contract_hash, CONTRACT_CODE_TEST, args) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let exec_request_4 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_hash(contract_hash, ADD_NEW_KEY_AS_SESSION, args) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([4; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_2).expect_success().commit(); + + builder.exec(exec_request_3).expect_success().commit(); + + builder.exec(exec_request_4).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let _package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let _new_key = account + .named_keys() + .get(NEW_KEY) + .expect("new key should be there"); +} + +#[ignore] +#[test] +fn should_not_call_session_from_contract() { + // This test runs a contract that extends the same key with more data after every call. + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_HEADERS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let contract_package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .cloned() + .expect("should have contract package"); + + let exec_request_2 = { + let args = runtime_args! { + PACKAGE_HASH_KEY => contract_package_hash, + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + SESSION_CODE_CALLER_AS_CONTRACT, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_2).commit(); + + let response = builder + .get_exec_responses() + .last() + .expect("should have last response"); + assert_eq!(response.len(), 1); + let exec_response = response.last().expect("should have response"); + let error = exec_response.as_error().expect("should have error"); + assert_matches!(error, Error::Exec(execution::Error::InvalidContext)); +} diff --git a/grpc/tests/src/test/contract_headers.rs b/grpc/tests/src/test/contract_headers.rs new file mode 100644 index 0000000000..c9ec0dcd36 --- /dev/null +++ b/grpc/tests/src/test/contract_headers.rs @@ -0,0 +1,125 @@ +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{runtime_args, Key, RuntimeArgs, SemVer}; + +const CONTRACT_HEADERS: &str = "contract_headers.wasm"; +const PACKAGE_HASH_KEY: &str = "package_hash_key"; +const PACKAGE_ACCESS_KEY: &str = "package_access_key"; +const STEP_1: i32 = 5; +const STEP_2: i32 = 6; +const STEP_3: i32 = 42; + +#[ignore] +#[test] +fn should_enforce_intended_execution_contexts() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_HEADERS, + RuntimeArgs::default(), + ) + .build(); + + let exec_request_2 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + SemVer::V1_0_0, + "session_code_test", + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let exec_request_3 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + SemVer::V1_0_0, + "contract_code_test", + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let exec_request_4 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + SemVer::V1_0_0, + "add_new_key_as_session", + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([4; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + builder.exec(exec_request_2).expect_success().commit(); + + builder.exec(exec_request_3).expect_success().commit(); + + builder.exec(exec_request_4).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let _foo = builder + .get_exec_response(3) + .expect("should have exec response"); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let new_key = account + .named_keys() + .get("new_key") + .expect("new key should be there"); +} diff --git a/grpc/tests/src/test/counter.rs b/grpc/tests/src/test/counter.rs new file mode 100644 index 0000000000..19cbe7f3f3 --- /dev/null +++ b/grpc/tests/src/test/counter.rs @@ -0,0 +1,197 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{runtime_args, Key, RuntimeArgs}; + +const CONTRACT_COUNTER_DEFINE: &str = "counter_define.wasm"; +const HASH_KEY_NAME: &str = "counter_package_hash"; +const COUNTER_VALUE_UREF: &str = "counter"; +const ENTRYPOINT_COUNTER: &str = "counter"; +const ENTRYPOINT_SESSION: &str = "session"; +const COUNTER_CONTRACT_HASH_KEY_NAME: &str = "counter_contract_hash"; +const ARG_COUNTER_METHOD: &str = "method"; +const METHOD_INC: &str = "inc"; + +#[ignore] +#[test] +fn should_run_counter_example_contract() { + let mut builder = InMemoryWasmTestBuilder::default(); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_COUNTER_DEFINE, + RuntimeArgs::new(), + ) + .build(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .expect("should be account") + .clone(); + + let counter_contract_hash_key = *account + .named_keys() + .get(COUNTER_CONTRACT_HASH_KEY_NAME) + .expect("should have counter contract hash key"); + + let exec_request_2 = ExecuteRequestBuilder::versioned_contract_call_by_hash_key_name( + DEFAULT_ACCOUNT_ADDR, + HASH_KEY_NAME, + None, + ENTRYPOINT_SESSION, + runtime_args! { COUNTER_CONTRACT_HASH_KEY_NAME => counter_contract_hash_key }, + ) + .build(); + + builder.exec(exec_request_2).expect_success().commit(); + + let value: i32 = builder + .query(None, counter_contract_hash_key, &[COUNTER_VALUE_UREF]) + .expect("should have counter value") + .as_cl_value() + .expect("should be CLValue") + .clone() + .into_t() + .expect("should cast CLValue to integer"); + + assert_eq!(value, 1); + + let exec_request_3 = ExecuteRequestBuilder::versioned_contract_call_by_hash_key_name( + DEFAULT_ACCOUNT_ADDR, + HASH_KEY_NAME, + None, + ENTRYPOINT_SESSION, + runtime_args! { COUNTER_CONTRACT_HASH_KEY_NAME => counter_contract_hash_key }, + ) + .build(); + + builder.exec(exec_request_3).expect_success().commit(); + + let value: i32 = builder + .query(None, counter_contract_hash_key, &[COUNTER_VALUE_UREF]) + .expect("should have counter value") + .as_cl_value() + .expect("should be CLValue") + .clone() + .into_t() + .expect("should cast CLValue to integer"); + + assert_eq!(value, 2); +} + +#[ignore] +#[test] +fn should_default_contract_hash_arg() { + let mut builder = InMemoryWasmTestBuilder::default(); + + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_COUNTER_DEFINE, + RuntimeArgs::new(), + ) + .build(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit(); + + let exec_request_2 = ExecuteRequestBuilder::versioned_contract_call_by_hash_key_name( + DEFAULT_ACCOUNT_ADDR, + HASH_KEY_NAME, + None, + ENTRYPOINT_SESSION, + RuntimeArgs::new(), + ) + .build(); + + builder.exec(exec_request_2).expect_success().commit(); + + let value: i32 = { + let counter_contract_hash_key = *builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .expect("should be account") + .clone() + .named_keys() + .get(COUNTER_CONTRACT_HASH_KEY_NAME) + .expect("should have counter contract hash key"); + + builder + .query(None, counter_contract_hash_key, &[COUNTER_VALUE_UREF]) + .expect("should have counter value") + .as_cl_value() + .expect("should be CLValue") + .clone() + .into_t() + .expect("should cast CLValue to integer") + }; + + assert_eq!(value, 1); +} + +#[ignore] +#[test] +fn should_call_counter_contract_directly() { + let mut builder = InMemoryWasmTestBuilder::default(); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_COUNTER_DEFINE, + RuntimeArgs::new(), + ) + .build(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit(); + + let exec_request_2 = ExecuteRequestBuilder::versioned_contract_call_by_hash_key_name( + DEFAULT_ACCOUNT_ADDR, + HASH_KEY_NAME, + None, + ENTRYPOINT_COUNTER, + runtime_args! { ARG_COUNTER_METHOD => METHOD_INC }, + ) + .build(); + + builder.exec(exec_request_2).expect_success().commit(); + + let value: i32 = { + let counter_contract_hash_key = *builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .expect("should be account") + .clone() + .named_keys() + .get(COUNTER_CONTRACT_HASH_KEY_NAME) + .expect("should have counter contract hash key"); + + builder + .query(None, counter_contract_hash_key, &[COUNTER_VALUE_UREF]) + .expect("should have counter value") + .as_cl_value() + .expect("should be CLValue") + .clone() + .into_t() + .expect("should cast CLValue to integer") + }; + + assert_eq!(value, 1); +} diff --git a/grpc/tests/src/test/deploy/mod.rs b/grpc/tests/src/test/deploy/mod.rs new file mode 100644 index 0000000000..c8d58474ae --- /dev/null +++ b/grpc/tests/src/test/deploy/mod.rs @@ -0,0 +1,3 @@ +mod non_standard_payment; +mod preconditions; +mod stored_contracts; diff --git a/grpc/tests/src/test/deploy/non_standard_payment.rs b/grpc/tests/src/test/deploy/non_standard_payment.rs new file mode 100644 index 0000000000..1e2647ce13 --- /dev/null +++ b/grpc/tests/src/test/deploy/non_standard_payment.rs @@ -0,0 +1,138 @@ +use engine_test_support::{ + internal::{ + utils, DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, + DEFAULT_ACCOUNT_KEY, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::CONV_RATE; +use node::contract_shared::motes::Motes; +use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; + +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([42u8; 32]); +const DO_NOTHING_WASM: &str = "do_nothing.wasm"; +const TRANSFER_PURSE_TO_ACCOUNT_WASM: &str = "transfer_purse_to_account.wasm"; +const TRANSFER_MAIN_PURSE_TO_NEW_PURSE_WASM: &str = "transfer_main_purse_to_new_purse.wasm"; +const NAMED_PURSE_PAYMENT_WASM: &str = "named_purse_payment.wasm"; +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; +const ARG_PURSE_NAME: &str = "purse_name"; +const ARG_DESTINATION: &str = "destination"; + +#[ignore] +#[test] +fn should_charge_non_main_purse() { + // as account_1, create & fund a new purse and use that to pay for something + // instead of account_1 main purse + const TEST_PURSE_NAME: &str = "test-purse"; + + let account_1_account_hash = ACCOUNT_1_ADDR; + let payment_purse_amount = U512::from(10_000_000); + let account_1_funding_amount = U512::from(100_000_000); + let account_1_purse_funding_amount = U512::from(50_000_000); + + let mut builder = InMemoryWasmTestBuilder::default(); + + let setup_exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code( + TRANSFER_PURSE_TO_ACCOUNT_WASM, // creates account_1 + runtime_args! { + ARG_TARGET => account_1_account_hash, + ARG_AMOUNT => account_1_funding_amount + }, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => payment_purse_amount}) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([1; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let create_purse_exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(ACCOUNT_1_ADDR) + .with_session_code( + TRANSFER_MAIN_PURSE_TO_NEW_PURSE_WASM, // creates test purse + runtime_args! { ARG_DESTINATION => TEST_PURSE_NAME, ARG_AMOUNT => account_1_purse_funding_amount }, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => payment_purse_amount}) + .with_authorization_keys(&[account_1_account_hash]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + // let transfer_result = + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(setup_exec_request).expect_success().commit(); + builder + .exec(create_purse_exec_request) + .expect_success() + .commit(); + let transfer_result = builder.finish(); + + // get account_1 + let account_1 = transfer_result + .builder() + .get_account(ACCOUNT_1_ADDR) + .expect("should have account"); + // get purse + let purse_key = account_1.named_keys()[TEST_PURSE_NAME]; + let purse = purse_key.into_uref().expect("should have uref"); + + let purse_starting_balance = builder.get_purse_balance(purse); + + assert_eq!( + purse_starting_balance, account_1_purse_funding_amount, + "purse should be funded with expected amount" + ); + + // should be able to pay for exec using new purse + let account_payment_exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(ACCOUNT_1_ADDR) + .with_session_code(DO_NOTHING_WASM, RuntimeArgs::default()) + .with_payment_code( + NAMED_PURSE_PAYMENT_WASM, + runtime_args! { + ARG_PURSE_NAME => TEST_PURSE_NAME, + ARG_AMOUNT => payment_purse_amount + }, + ) + .with_authorization_keys(&[account_1_account_hash]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let transfer_result = builder + .exec(account_payment_exec_request) + .expect_success() + .commit() + .finish(); + + let response = transfer_result + .builder() + .get_exec_response(2) + .expect("there should be a response") + .clone(); + + let result = utils::get_success_result(&response); + let gas = result.cost(); + let motes = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + + let expected_resting_balance = account_1_purse_funding_amount - motes.value(); + + let purse_final_balance = builder.get_purse_balance(purse); + + assert_eq!( + purse_final_balance, expected_resting_balance, + "purse resting balance should equal funding amount minus exec costs" + ); +} diff --git a/grpc/tests/src/test/deploy/preconditions.rs b/grpc/tests/src/test/deploy/preconditions.rs new file mode 100644 index 0000000000..2caf4ce3c2 --- /dev/null +++ b/grpc/tests/src/test/deploy/preconditions.rs @@ -0,0 +1,121 @@ +use assert_matches::assert_matches; + +use engine_test_support::{ + internal::{ + utils, DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::Error; +use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; + +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([42u8; 32]); +const ARG_AMOUNT: &str = "amount"; + +#[ignore] +#[test] +fn should_raise_precondition_authorization_failure_invalid_account() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let nonexistent_account_addr = AccountHash::new([99u8; 32]); + let payment_purse_amount = 10_000_000; + let transferred_amount = 1; + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_session_code( + "transfer_purse_to_account.wasm", + runtime_args! { "target" =>account_1_account_hash, "amount" => U512::from(transferred_amount) }, + ) + .with_address(nonexistent_account_addr) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount) }) + .with_authorization_keys(&[nonexistent_account_addr]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let transfer_result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .finish(); + + let response = transfer_result + .builder() + .get_exec_response(0) + .expect("there should be a response"); + + let precondition_failure = utils::get_precondition_failure(response); + assert_matches!(precondition_failure, Error::Authorization); +} + +#[ignore] +#[test] +fn should_raise_precondition_authorization_failure_empty_authorized_keys() { + let empty_keys: [AccountHash; 0] = []; + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code("do_nothing.wasm", RuntimeArgs::default()) + .with_empty_payment_bytes(RuntimeArgs::default()) + .with_deploy_hash([1; 32]) + // empty authorization keys to force error + .with_authorization_keys(&empty_keys) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let transfer_result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .finish(); + + let response = transfer_result + .builder() + .get_exec_response(0) + .expect("there should be a response"); + + let precondition_failure = utils::get_precondition_failure(response); + assert_matches!(precondition_failure, Error::Authorization); +} + +#[ignore] +#[test] +fn should_raise_precondition_authorization_failure_invalid_authorized_keys() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let nonexistent_account_addr = AccountHash::new([99u8; 32]); + let payment_purse_amount = 10_000_000; + let transferred_amount = 1; + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_session_code( + "transfer_purse_to_account.wasm", + runtime_args! { "target" =>account_1_account_hash, "amount" => U512::from(transferred_amount) }, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount) }) + // invalid authorization key to force error + .with_authorization_keys(&[nonexistent_account_addr]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let transfer_result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .finish(); + + let response = transfer_result + .builder() + .get_exec_response(0) + .expect("there should be a response"); + + let precondition_failure = utils::get_precondition_failure(response); + assert_matches!(precondition_failure, Error::Authorization); +} diff --git a/grpc/tests/src/test/deploy/stored_contracts.rs b/grpc/tests/src/test/deploy/stored_contracts.rs new file mode 100644 index 0000000000..cdf32af2e7 --- /dev/null +++ b/grpc/tests/src/test/deploy/stored_contracts.rs @@ -0,0 +1,1208 @@ +use std::collections::BTreeMap; + +use engine_grpc_server::engine_server::ipc::DeployCode; +use engine_test_support::{ + internal::{ + utils, AdditiveMapDiff, DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, + UpgradeRequestBuilder, WasmTestBuilder, DEFAULT_ACCOUNT_KEY, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, +}; +use node::contract_core::engine_state::{upgrade::ActivationPoint, CONV_RATE}; +use node::contract_shared::{ + account::Account, motes::Motes, stored_value::StoredValue, transform::Transform, +}; +use node::contract_storage::global_state::in_memory::InMemoryGlobalState; +use types::{ + account::AccountHash, + contracts::{ContractVersion, CONTRACT_INITIAL_VERSION, DEFAULT_ENTRY_POINT_NAME}, + runtime_args, ContractHash, Key, ProtocolVersion, RuntimeArgs, U512, +}; + +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([42u8; 32]); +const DEFAULT_ACTIVATION_POINT: ActivationPoint = 1; +const DO_NOTHING_NAME: &str = "do_nothing"; +const DO_NOTHING_CONTRACT_PACKAGE_HASH_NAME: &str = "do_nothing_package_hash"; +const DO_NOTHING_CONTRACT_HASH_NAME: &str = "do_nothing_hash"; +const INITIAL_VERSION: ContractVersion = CONTRACT_INITIAL_VERSION; +const ENTRY_FUNCTION_NAME: &str = "delegate"; +const MODIFIED_MINT_UPGRADER_CONTRACT_NAME: &str = "modified_mint_upgrader.wasm"; +const MODIFIED_SYSTEM_UPGRADER_CONTRACT_NAME: &str = "modified_system_upgrader.wasm"; +const PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::V1_0_0; +const STORED_PAYMENT_CONTRACT_NAME: &str = "test_payment_stored.wasm"; +const STORED_PAYMENT_CONTRACT_HASH_NAME: &str = "test_payment_hash"; +const STORED_PAYMENT_CONTRACT_PACKAGE_HASH_NAME: &str = "test_payment_package_hash"; +const PAY: &str = "pay"; +const TRANSFER: &str = "transfer"; +const TRANSFER_PURSE_TO_ACCOUNT_CONTRACT_NAME: &str = "transfer_purse_to_account"; +const TRANSFER_PURSE_TO_ACCOUNT_STORED_HASH_KEY_NAME: &str = "transfer_purse_to_account_hash"; +// Currently Error enum that holds this variant is private and can't be used otherwise to compare +// message +const EXPECTED_ERROR_MESSAGE: &str = "IncompatibleProtocolMajorVersion { expected: 2, actual: 1 }"; +const EXPECTED_VERSION_ERROR_MESSAGE: &str = "InvalidContractVersion(ContractVersionKey(2, 1))"; + +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +/// Prepares a upgrade request with pre-loaded deploy code, and new protocol version. +fn make_upgrade_request( + new_protocol_version: ProtocolVersion, + code: &str, +) -> UpgradeRequestBuilder { + let installer_code = { + let bytes = utils::read_wasm_file_bytes(code); + let mut deploy_code = DeployCode::new(); + deploy_code.set_code(bytes); + deploy_code + }; + + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(new_protocol_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .with_installer_code(installer_code) +} + +fn store_payment_to_account_context( + builder: &mut WasmTestBuilder, +) -> (Account, ContractHash) { + // store payment contract + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + STORED_PAYMENT_CONTRACT_NAME, + RuntimeArgs::default(), + ) + .build(); + + builder.exec_commit_finish(exec_request); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + // check account named keys + let hash = default_account + .named_keys() + .get(STORED_PAYMENT_CONTRACT_PACKAGE_HASH_NAME) + .expect("key should exist") + .into_hash() + .expect("should be a hash"); + + (default_account, hash) +} + +#[ignore] +#[test] +fn should_exec_non_stored_code() { + // using the new execute logic, passing code for both payment and session + // should work exactly as it did with the original exec logic + + let account_1_account_hash = ACCOUNT_1_ADDR; + let payment_purse_amount = 10_000_000; + let transferred_amount = 1; + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code( + &format!("{}.wasm", TRANSFER_PURSE_TO_ACCOUNT_CONTRACT_NAME), + runtime_args! { + ARG_TARGET => account_1_account_hash, + ARG_AMOUNT => U512::from(transferred_amount) + }, + ) + .with_empty_payment_bytes(runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([1; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let test_result = builder.exec_commit_finish(exec_request); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get genesis account"); + let modified_balance: U512 = builder.get_purse_balance(default_account.main_purse()); + + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + + assert_ne!( + modified_balance, initial_balance, + "balance should be less than initial balance" + ); + + let response = test_result + .builder() + .get_exec_response(0) + .expect("there should be a response") + .clone(); + + let success_result = utils::get_success_result(&response); + let gas = success_result.cost(); + let motes = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + let tally = motes.value() + U512::from(transferred_amount) + modified_balance; + + assert_eq!( + initial_balance, tally, + "no net resources should be gained or lost post-distribution" + ); +} + +#[ignore] +#[test] +fn should_exec_stored_code_by_hash() { + let payment_purse_amount = 10_000_000; + + // genesis + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + // store payment + let (default_account, hash) = store_payment_to_account_context(&mut builder); + + // verify stored contract functions as expected by checking all the maths + + let (motes_alpha, modified_balance_alpha) = { + // get modified balance + let modified_balance_alpha: U512 = builder.get_purse_balance(default_account.main_purse()); + + // get cost + let response = builder + .get_exec_response(0) + .expect("there should be a response") + .clone(); + let result = utils::get_success_result(&response); + let gas = result.cost(); + let motes_alpha = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + (motes_alpha, modified_balance_alpha) + }; + + let transferred_amount = 1; + + // next make another deploy that USES stored payment logic + { + let exec_request_stored_payment = { + let account_1_account_hash = ACCOUNT_1_ADDR; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code( + &format!("{}.wasm", TRANSFER_PURSE_TO_ACCOUNT_CONTRACT_NAME), + runtime_args! { ARG_TARGET => account_1_account_hash, ARG_AMOUNT => U512::from(transferred_amount) }, + ) + .with_stored_versioned_payment_contract_by_hash( + hash, + Some(CONTRACT_INITIAL_VERSION), + PAY, + runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec_commit_finish(exec_request_stored_payment); + } + + let (motes_bravo, modified_balance_bravo) = { + let modified_balance_bravo: U512 = builder.get_purse_balance(default_account.main_purse()); + + let response = builder + .get_exec_response(1) + .expect("there should be a response") + .clone(); + + let result = utils::get_success_result(&response); + let gas = result.cost(); + let motes_bravo = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + + (motes_bravo, modified_balance_bravo) + }; + + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + + assert!( + modified_balance_alpha < initial_balance, + "balance should be less than initial balance" + ); + + assert!( + modified_balance_bravo < modified_balance_alpha, + "second modified balance should be less than first modified balance" + ); + + let tally = motes_alpha.value() + + motes_bravo.value() + + U512::from(transferred_amount) + + modified_balance_bravo; + + assert_eq!( + initial_balance, tally, + "no net resources should be gained or lost post-distribution" + ); +} + +#[ignore] +#[test] +fn should_exec_stored_code_by_named_hash() { + let payment_purse_amount = 10_000_000; + + // genesis + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + // store payment + let (default_account, _) = store_payment_to_account_context(&mut builder); + + // verify stored contract functions as expected by checking all the maths + + let (motes_alpha, modified_balance_alpha) = { + // get modified balance + let modified_balance_alpha: U512 = builder.get_purse_balance(default_account.main_purse()); + + // get cost + let response = builder + .get_exec_response(0) + .expect("there should be a response") + .clone(); + let result = utils::get_success_result(&response); + let gas = result.cost(); + let motes_alpha = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + (motes_alpha, modified_balance_alpha) + }; + + let transferred_amount = 1; + + // next make another deploy that USES stored payment logic + { + let exec_request_stored_payment = { + let account_1_account_hash = ACCOUNT_1_ADDR; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code( + &format!("{}.wasm", TRANSFER_PURSE_TO_ACCOUNT_CONTRACT_NAME), + runtime_args! { ARG_TARGET => account_1_account_hash, ARG_AMOUNT => U512::from(transferred_amount) }, + ) + .with_stored_versioned_payment_contract_by_name( + STORED_PAYMENT_CONTRACT_PACKAGE_HASH_NAME, + Some(CONTRACT_INITIAL_VERSION), + PAY, + runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec_commit_finish(exec_request_stored_payment); + } + + let (motes_bravo, modified_balance_bravo) = { + let modified_balance_bravo: U512 = builder.get_purse_balance(default_account.main_purse()); + + let response = builder + .get_exec_response(1) + .expect("there should be a response") + .clone(); + + let result = utils::get_success_result(&response); + let gas = result.cost(); + let motes_bravo = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + + (motes_bravo, modified_balance_bravo) + }; + + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + + assert!( + modified_balance_alpha < initial_balance, + "balance should be less than initial balance" + ); + + assert!( + modified_balance_bravo < modified_balance_alpha, + "second modified balance should be less than first modified balance" + ); + + let tally = motes_alpha.value() + + motes_bravo.value() + + U512::from(transferred_amount) + + modified_balance_bravo; + + assert_eq!( + initial_balance, tally, + "no net resources should be gained or lost post-distribution" + ); +} + +#[ignore] +#[test] +fn should_exec_payment_and_session_stored_code() { + let payment_purse_amount = 100_000_000; // <- seems like a lot, but it gets spent fast! + + // genesis + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + // store payment + store_payment_to_account_context(&mut builder); + + // verify stored contract functions as expected by checking all the maths + + let motes_alpha = { + // get modified balance + + // get cost + let response = builder + .get_exec_response(0) + .expect("there should be a response") + .clone(); + let result = utils::get_success_result(&response); + let gas = result.cost(); + Motes::from_gas(gas, CONV_RATE).expect("should have motes") + }; + + // next store transfer contract + let exec_request_store_transfer = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code( + &format!("{}_stored.wasm", TRANSFER_PURSE_TO_ACCOUNT_CONTRACT_NAME), + RuntimeArgs::default(), + ) + .with_stored_versioned_payment_contract_by_name( + STORED_PAYMENT_CONTRACT_PACKAGE_HASH_NAME, + Some(CONTRACT_INITIAL_VERSION), + PAY, + runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let test_result = builder.exec_commit_finish(exec_request_store_transfer); + + let motes_bravo = { + let response = test_result + .builder() + .get_exec_response(1) + .expect("there should be a response") + .clone(); + + let result = utils::get_success_result(&response); + let gas = result.cost(); + Motes::from_gas(gas, CONV_RATE).expect("should have motes") + }; + + let transferred_amount = 1; + + // next make another deploy that USES stored payment logic & stored transfer + // logic + let exec_request_stored_only = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + TRANSFER_PURSE_TO_ACCOUNT_CONTRACT_NAME, + Some(CONTRACT_INITIAL_VERSION), + TRANSFER, + runtime_args! { + ARG_TARGET => ACCOUNT_1_ADDR, + ARG_AMOUNT => U512::from(transferred_amount), + }, + ) + .with_stored_versioned_payment_contract_by_name( + STORED_PAYMENT_CONTRACT_PACKAGE_HASH_NAME, + Some(CONTRACT_INITIAL_VERSION), + PAY, + runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let test_result = builder.exec_commit_finish(exec_request_stored_only); + + let motes_charlie = { + let response = test_result + .builder() + .get_exec_response(2) + .expect("there should be a response") + .clone(); + + let result = utils::get_success_result(&response); + let gas = result.cost(); + Motes::from_gas(gas, CONV_RATE).expect("should have motes") + }; + + let modified_balance: U512 = { + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get genesis account"); + builder.get_purse_balance(default_account.main_purse()) + }; + + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + + let tally = motes_alpha.value() + + motes_bravo.value() + + motes_charlie.value() + + U512::from(transferred_amount) + + modified_balance; + + assert_eq!( + initial_balance, tally, + "no net resources should be gained or lost post-distribution" + ); +} + +#[ignore] +#[test] +fn should_have_equivalent_transforms_with_stored_contract_pointers() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let payment_purse_amount = 100_000_000; + let transferred_amount = 1; + + let stored_transforms = { + let mut builder = InMemoryWasmTestBuilder::default(); + + let exec_request_1 = { + let store_transfer = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code( + &format!("{}_stored.wasm", TRANSFER_PURSE_TO_ACCOUNT_CONTRACT_NAME), + RuntimeArgs::default(), + ) + .with_empty_payment_bytes(runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([1; 32]) + .build(); + + ExecuteRequestBuilder::new() + .push_deploy(store_transfer) + .build() + }; + + let exec_request_2 = { + let store_transfer = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code(STORED_PAYMENT_CONTRACT_NAME, RuntimeArgs::default()) + .with_empty_payment_bytes(runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new() + .push_deploy(store_transfer) + .build() + }; + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit(); + + builder.exec(exec_request_2).expect_success().commit(); + + let call_stored_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_named_key( + TRANSFER_PURSE_TO_ACCOUNT_STORED_HASH_KEY_NAME, + TRANSFER, + runtime_args! { ARG_TARGET => account_1_account_hash, ARG_AMOUNT => U512::from(transferred_amount) }, + ) + .with_stored_payment_named_key( + STORED_PAYMENT_CONTRACT_HASH_NAME, + PAY, + runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder + .exec(call_stored_request) + .expect_success() + .commit() + .get_transforms()[2] + .to_owned() + }; + + let provided_transforms = { + let do_nothing_request = |deploy_hash: [u8; 32]| { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code(&format!("{}.wasm", DO_NOTHING_NAME), RuntimeArgs::default()) + .with_empty_payment_bytes( + runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount), }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash(deploy_hash) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let provided_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code( + &format!("{}.wasm", TRANSFER_PURSE_TO_ACCOUNT_CONTRACT_NAME), + runtime_args! { ARG_TARGET => account_1_account_hash, ARG_AMOUNT => U512::from(transferred_amount) }, + ) + .with_empty_payment_bytes( + runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder + .exec(do_nothing_request([1; 32])) + .expect_success() + .commit(); + builder + .exec(do_nothing_request([2; 32])) + .expect_success() + .commit(); + + builder + .exec(provided_request) + .expect_success() + .get_transforms()[2] + .to_owned() + }; + + let diff = AdditiveMapDiff::new(provided_transforms, stored_transforms); + + let left: BTreeMap<&Key, &Transform> = diff.left().iter().collect(); + let right: BTreeMap<&Key, &Transform> = diff.right().iter().collect(); + + // The diff contains the same keys... + assert!(Iterator::eq(left.keys(), right.keys())); + + // ...but a few different values + for lr in left.values().zip(right.values()) { + match lr { + ( + Transform::Write(StoredValue::CLValue(l_value)), + Transform::Write(StoredValue::CLValue(r_value)), + ) => { + // differing refunds and balances + let _ = l_value.to_owned().into_t::().expect("should be U512"); + let _ = r_value.to_owned().into_t::().expect("should be U512"); + } + ( + Transform::Write(StoredValue::Account(la)), + Transform::Write(StoredValue::Account(ra)), + ) => { + assert_eq!(la.account_hash(), ra.account_hash()); + assert_eq!(la.main_purse(), ra.main_purse()); + assert_eq!(la.action_thresholds(), ra.action_thresholds()); + + assert!(Iterator::eq( + la.get_associated_keys(), + ra.get_associated_keys(), + )); + + // la has stored contracts under named urefs + assert_ne!(la.named_keys(), ra.named_keys()); + } + (Transform::AddUInt512(_), Transform::AddUInt512(_)) => { + // differing payment + } + _ => { + panic!("unexpected diff"); + } + } + } +} + +#[ignore] +#[test] +fn should_fail_payment_stored_at_named_key_with_incompatible_major_version() { + let payment_purse_amount = 10_000_000; + + // first, store payment contract + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + STORED_PAYMENT_CONTRACT_NAME, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec_commit_finish(exec_request); + + let query_result = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query default account"); + let default_account = query_result + .as_account() + .expect("query result should be an account"); + + assert!( + default_account + .named_keys() + .contains_key(STORED_PAYMENT_CONTRACT_HASH_NAME), + "standard_payment should be present" + ); + + // + // upgrade with new wasm costs with modified mint for given version to avoid missing wasm costs + // table that's queried early + // + let sem_ver = PROTOCOL_VERSION.value(); + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major + 1, sem_ver.minor, sem_ver.patch); + + let mut upgrade_request = + make_upgrade_request(new_protocol_version, MODIFIED_MINT_UPGRADER_CONTRACT_NAME).build(); + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!(upgrade_response.has_success(), "expected success"); + + // next make another deploy that USES stored payment logic + let exec_request_stored_payment = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code(&format!("{}.wasm", DO_NOTHING_NAME), RuntimeArgs::default()) + .with_stored_payment_named_key( + STORED_PAYMENT_CONTRACT_HASH_NAME, + PAY, + runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }, + ) + // .with_stored_versioned_payment_contract_by_name( + // STORED_PAYMENT_CONTRACT_PACKAGE_HASH_NAME, + // Some(CONTRACT_INITIAL_VERSION), + // PAY, + // runtime_args! { + // ARG_AMOUNT => U512::from(payment_purse_amount), + // }, + // ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new() + .push_deploy(deploy) + .with_protocol_version(new_protocol_version) + .build() + }; + + let test_result = builder.exec(exec_request_stored_payment).commit(); + + assert!( + test_result.is_error(), + "calling a payment module with increased major protocol version should be error" + ); + let error_message = builder + .exec_error_message(1) + .expect("should have exec error"); + assert!( + error_message.contains(EXPECTED_ERROR_MESSAGE), + "{:?}", + error_message + ); +} + +#[ignore] +#[test] +fn should_fail_payment_stored_at_hash_with_incompatible_major_version() { + let payment_purse_amount = 10_000_000; + + // first, store payment contract + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + STORED_PAYMENT_CONTRACT_NAME, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec_commit_finish(exec_request); + + let query_result = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query default account"); + let default_account = query_result + .as_account() + .expect("query result should be an account"); + let stored_payment_contract_hash = default_account + .named_keys() + .get(STORED_PAYMENT_CONTRACT_HASH_NAME) + .expect("should have standard_payment named key") + .into_hash() + .expect("standard_payment should be an uref"); + + // + // upgrade with new wasm costs with modified mint for given version to avoid missing wasm costs + // table that's queried early + // + let sem_ver = PROTOCOL_VERSION.value(); + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major + 1, sem_ver.minor, sem_ver.patch); + + let mut upgrade_request = + make_upgrade_request(new_protocol_version, MODIFIED_MINT_UPGRADER_CONTRACT_NAME).build(); + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!( + upgrade_response.has_success(), + "expected success: {:?}", + upgrade_response + ); + + // next make another deploy that USES stored payment logic + let exec_request_stored_payment = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code(&format!("{}.wasm", DO_NOTHING_NAME), RuntimeArgs::default()) + .with_stored_payment_hash( + stored_payment_contract_hash, + DEFAULT_ENTRY_POINT_NAME, + runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount) }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new() + .push_deploy(deploy) + .with_protocol_version(new_protocol_version) + .build() + }; + + let test_result = builder.exec(exec_request_stored_payment).commit(); + + assert!( + test_result.is_error(), + "calling a payment module with increased major protocol version should be error" + ); + let error_message = builder + .exec_error_message(1) + .expect("should have exec error"); + assert!(error_message.contains(EXPECTED_ERROR_MESSAGE)); +} + +#[ignore] +#[test] +fn should_fail_session_stored_at_named_key_with_incompatible_major_version() { + let payment_purse_amount = 10_000_000; + + // first, store payment contract for v1.0.0 + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &format!("{}_stored.wasm", DO_NOTHING_NAME), + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec_commit_finish(exec_request_1); + + let query_result = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query default account"); + let default_account = query_result + .as_account() + .expect("query result should be an account"); + assert!( + default_account + .named_keys() + .contains_key(DO_NOTHING_CONTRACT_HASH_NAME), + "do_nothing should be present in named keys" + ); + + // + // upgrade with new wasm costs with modified mint for given version + // + let sem_ver = PROTOCOL_VERSION.value(); + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major + 1, sem_ver.minor, sem_ver.patch); + + let mut upgrade_request = + make_upgrade_request(new_protocol_version, MODIFIED_MINT_UPGRADER_CONTRACT_NAME).build(); + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!(upgrade_response.has_success(), "expected success"); + + // Call stored session code + + let exec_request_stored_payment = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_named_key( + DO_NOTHING_CONTRACT_HASH_NAME, + ENTRY_FUNCTION_NAME, + RuntimeArgs::new(), + ) + .with_payment_code( + STORED_PAYMENT_CONTRACT_NAME, + runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new() + .push_deploy(deploy) + .with_protocol_version(new_protocol_version) + .build() + }; + + let test_result = builder.exec(exec_request_stored_payment).commit(); + + assert!( + test_result.is_error(), + "calling a session module with increased major protocol version should be error", + ); + let error_message = builder + .exec_error_message(1) + .expect("should have exec error"); + assert!( + error_message.contains(EXPECTED_ERROR_MESSAGE), + "{:?}", + error_message + ); +} + +#[ignore] +#[test] +fn should_fail_session_stored_at_named_key_with_missing_new_major_version() { + let payment_purse_amount = 10_000_000; + + // first, store payment contract for v1.0.0 + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &format!("{}_stored.wasm", DO_NOTHING_NAME), + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec_commit_finish(exec_request_1); + + let query_result = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query default account"); + let default_account = query_result + .as_account() + .expect("query result should be an account"); + assert!( + default_account + .named_keys() + .contains_key(DO_NOTHING_CONTRACT_HASH_NAME), + "do_nothing should be present in named keys" + ); + + // + // upgrade with new wasm costs with modified mint for given version + // + let sem_ver = PROTOCOL_VERSION.value(); + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major + 1, sem_ver.minor, sem_ver.patch); + + let mut upgrade_request = + make_upgrade_request(new_protocol_version, MODIFIED_MINT_UPGRADER_CONTRACT_NAME).build(); + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!(upgrade_response.has_success(), "expected success"); + + // Call stored session code + + let exec_request_stored_payment = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + DO_NOTHING_CONTRACT_PACKAGE_HASH_NAME, + Some(INITIAL_VERSION), + ENTRY_FUNCTION_NAME, + RuntimeArgs::new(), + ) + .with_payment_code( + STORED_PAYMENT_CONTRACT_NAME, + runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new() + .push_deploy(deploy) + .with_protocol_version(new_protocol_version) + .build() + }; + + let test_result = builder.exec(exec_request_stored_payment).commit(); + + assert!( + test_result.is_error(), + "calling a session module with increased major protocol version should be error", + ); + let error_message = builder + .exec_error_message(1) + .expect("should have exec error"); + assert!( + error_message.contains(EXPECTED_VERSION_ERROR_MESSAGE), + "{:?}", + error_message + ); +} + +#[ignore] +#[test] +fn should_fail_session_stored_at_hash_with_incompatible_major_version() { + let payment_purse_amount = 10_000_000; + + // first, store payment contract for v1.0.0 + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &format!("{}_stored.wasm", DO_NOTHING_NAME), + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec_commit_finish(exec_request_1); + + // + // upgrade with new wasm costs with modified mint for given version + // + let sem_ver = PROTOCOL_VERSION.value(); + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major + 1, sem_ver.minor, sem_ver.patch); + + let mut upgrade_request = + make_upgrade_request(new_protocol_version, MODIFIED_MINT_UPGRADER_CONTRACT_NAME).build(); + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!(upgrade_response.has_success(), "expected success"); + + // Call stored session code + + let exec_request_stored_payment = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_named_key( + DO_NOTHING_CONTRACT_HASH_NAME, + ENTRY_FUNCTION_NAME, + RuntimeArgs::new(), + ) + .with_payment_code( + STORED_PAYMENT_CONTRACT_NAME, + runtime_args! { + ARG_AMOUNT => U512::from(payment_purse_amount), + }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new() + .push_deploy(deploy) + .with_protocol_version(new_protocol_version) + .build() + }; + + let test_result = builder.exec(exec_request_stored_payment).commit(); + + assert!( + test_result.is_error(), + "calling a session module with increased major protocol version should be error", + ); + let error_message = builder + .exec_error_message(1) + .expect("should have exec error"); + assert!( + error_message.contains(EXPECTED_ERROR_MESSAGE), + "{:?}", + error_message + ); +} + +#[ignore] +#[test] +fn should_execute_stored_payment_and_session_code_with_new_major_version() { + let payment_purse_amount = 10_000_000; + + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + // + // upgrade with new wasm costs with modified mint for given version + // + let sem_ver = PROTOCOL_VERSION.value(); + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major + 1, sem_ver.minor, sem_ver.patch); + + let mut upgrade_request = + make_upgrade_request(new_protocol_version, MODIFIED_SYSTEM_UPGRADER_CONTRACT_NAME).build(); + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!( + upgrade_response.has_success(), + "expected success but {:?}", + upgrade_response + ); + + // first, store payment contract for v2.0.0 + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + STORED_PAYMENT_CONTRACT_NAME, + RuntimeArgs::default(), + ) + .with_protocol_version(new_protocol_version) + .build(); + + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &format!("{}_stored.wasm", DO_NOTHING_NAME), + RuntimeArgs::default(), + ) + .with_protocol_version(new_protocol_version) + .build(); + + // store both contracts + builder.exec(exec_request_1).expect_success().commit(); + + let test_result = builder + .exec(exec_request_2) + .expect_success() + .commit() + .finish(); + + // query both stored contracts by their named keys + let query_result = test_result + .builder() + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query default account"); + let default_account = query_result + .as_account() + .expect("query result should be an account"); + let test_payment_stored_hash = default_account + .named_keys() + .get(STORED_PAYMENT_CONTRACT_HASH_NAME) + .expect("standard_payment should be present in named keys") + .into_hash() + .expect("standard_payment named key should be hash"); + + let exec_request_stored_payment = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + DO_NOTHING_CONTRACT_PACKAGE_HASH_NAME, + Some(INITIAL_VERSION), + ENTRY_FUNCTION_NAME, + RuntimeArgs::new(), + ) + .with_stored_payment_hash( + test_payment_stored_hash, + "pay", + runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount) }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new() + .push_deploy(deploy) + .with_protocol_version(new_protocol_version) + .build() + }; + + InMemoryWasmTestBuilder::from_result(test_result) + .exec(exec_request_stored_payment) + .expect_success() + .commit(); +} diff --git a/grpc/tests/src/test/explorer/faucet.rs b/grpc/tests/src/test/explorer/faucet.rs new file mode 100644 index 0000000000..f496618bae --- /dev/null +++ b/grpc/tests/src/test/explorer/faucet.rs @@ -0,0 +1,76 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{account::AccountHash, runtime_args, ApiError, RuntimeArgs, U512}; + +const FAUCET_CONTRACT: &str = "faucet.wasm"; +const NEW_ACCOUNT_ADDR: AccountHash = AccountHash::new([99u8; 32]); + +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +#[ignore] +#[test] +fn should_get_funds_from_faucet() { + let amount = U512::from(1000); + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + FAUCET_CONTRACT, + runtime_args! { ARG_TARGET => NEW_ACCOUNT_ADDR, ARG_AMOUNT => amount }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + builder + .run_genesis(&*DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .expect_success() + .commit(); + + let account = builder + .get_account(NEW_ACCOUNT_ADDR) + .expect("should get account"); + + let account_purse = account.main_purse(); + let account_balance = builder.get_purse_balance(account_purse); + assert_eq!( + account_balance, amount, + "faucet should have created account with requested amount" + ); +} + +#[ignore] +#[test] +fn should_fail_if_already_funded() { + let amount = U512::from(1000); + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + FAUCET_CONTRACT, + runtime_args! { ARG_TARGET => NEW_ACCOUNT_ADDR, ARG_AMOUNT => amount }, + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + FAUCET_CONTRACT, + runtime_args! { ARG_TARGET => NEW_ACCOUNT_ADDR, ARG_AMOUNT => amount }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&*DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2); // should fail + + let error_msg = builder + .exec_error_message(1) + .expect("should have error message"); + assert!( + error_msg.contains(&format!("{:?}", ApiError::User(1))), + error_msg + ); +} diff --git a/grpc/tests/src/test/explorer/faucet_stored.rs b/grpc/tests/src/test/explorer/faucet_stored.rs new file mode 100644 index 0000000000..c78b161d02 --- /dev/null +++ b/grpc/tests/src/test/explorer/faucet_stored.rs @@ -0,0 +1,143 @@ +use engine_test_support::{ + internal::{ + utils, DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, + DEFAULT_ACCOUNT_KEY, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{account::AccountHash, runtime_args, ApiError, RuntimeArgs, U512}; + +const FAUCET: &str = "faucet"; +const CALL_FAUCET: &str = "call_faucet"; +const NEW_ACCOUNT_ADDR: AccountHash = AccountHash::new([99u8; 32]); + +fn get_builder() -> InMemoryWasmTestBuilder { + let mut builder = InMemoryWasmTestBuilder::default(); + { + // first, store contract + let store_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &format!("{}_stored.wasm", FAUCET), + runtime_args! {}, + ) + .build(); + + builder.run_genesis(&*DEFAULT_RUN_GENESIS_REQUEST); + builder.exec_commit_finish(store_request); + } + builder +} + +#[ignore] +#[test] +fn should_get_funds_from_faucet_stored() { + let mut builder = get_builder(); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + let contract_hash = default_account + .named_keys() + .get(FAUCET) + .expect("contract_hash should exist") + .into_hash() + .expect("should be a hash"); + + let amount = U512::from(1000); + + // call stored faucet + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_hash( + contract_hash, + CALL_FAUCET, + runtime_args! { "target" => NEW_ACCOUNT_ADDR, "amount" => amount }, + ) + .with_empty_payment_bytes(runtime_args! { "amount" => U512::from(10_000_000) }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + builder.exec(exec_request).expect_success().commit(); + + let account = builder + .get_account(NEW_ACCOUNT_ADDR) + .expect("should get account"); + + let account_purse = account.main_purse(); + let account_balance = builder.get_purse_balance(account_purse); + assert_eq!( + account_balance, amount, + "faucet should have created account with requested amount" + ); +} + +#[ignore] +#[test] +fn should_fail_if_already_funded() { + let mut builder = get_builder(); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + let contract_hash = default_account + .named_keys() + .get(FAUCET) + .expect("contract_hash should exist") + .into_hash() + .expect("should be a hash"); + + let amount = U512::from(1000); + + // call stored faucet + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_hash( + contract_hash, + CALL_FAUCET, + runtime_args! { "target" => NEW_ACCOUNT_ADDR, "amount" => amount }, + ) + .with_empty_payment_bytes(runtime_args! { "amount" => U512::from(10_000_000) }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + builder.exec(exec_request).expect_success().commit(); + + // call stored faucet again; should error + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_session_hash( + contract_hash, + CALL_FAUCET, + runtime_args! { "target" => NEW_ACCOUNT_ADDR, "amount" => amount }, + ) + .with_empty_payment_bytes(runtime_args! { "amount" => U512::from(10_000_000) }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request); + + let exec_response = builder + .get_exec_response(2) + .expect("Expected to be called after run()"); + + let error_message = utils::get_error_message(exec_response); + assert!( + error_message.contains(&format!("{:?}", ApiError::User(1))), + "should have reverted with user error 1 (already funded)" + ); +} diff --git a/grpc/tests/src/test/explorer/mod.rs b/grpc/tests/src/test/explorer/mod.rs new file mode 100644 index 0000000000..56fbcfc078 --- /dev/null +++ b/grpc/tests/src/test/explorer/mod.rs @@ -0,0 +1,2 @@ +mod faucet; +mod faucet_stored; diff --git a/grpc/tests/src/test/groups.rs b/grpc/tests/src/test/groups.rs new file mode 100644 index 0000000000..a35d4aa61a --- /dev/null +++ b/grpc/tests/src/test/groups.rs @@ -0,0 +1,921 @@ +use assert_matches::assert_matches; +use lazy_static::lazy_static; + +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::{engine_state::Error, execution}; +use types::{ + account::AccountHash, contracts::CONTRACT_INITIAL_VERSION, runtime_args, Key, RuntimeArgs, U512, +}; + +const CONTRACT_GROUPS: &str = "groups.wasm"; +const PACKAGE_HASH_KEY: &str = "package_hash_key"; +const PACKAGE_ACCESS_KEY: &str = "package_access_key"; +const RESTRICTED_SESSION: &str = "restricted_session"; +const RESTRICTED_CONTRACT: &str = "restricted_contract"; +const RESTRICTED_SESSION_CALLER: &str = "restricted_session_caller"; +const UNRESTRICTED_CONTRACT_CALLER: &str = "unrestricted_contract_caller"; +const PACKAGE_HASH_ARG: &str = "package_hash"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const CONTRACT_TRANSFER_TO_ACCOUNT: &str = "transfer_to_account_u512.wasm"; +const RESTRICTED_CONTRACT_CALLER_AS_SESSION: &str = "restricted_contract_caller_as_session"; +const UNCALLABLE_SESSION: &str = "uncallable_session"; +const UNCALLABLE_CONTRACT: &str = "uncallable_contract"; +const CALL_RESTRICTED_ENTRY_POINTS: &str = "call_restricted_entry_points"; +const ARG_AMOUNT: &str = "amount"; +const ARG_TARGET: &str = "target"; + +lazy_static! { + static ref TRANSFER_1_AMOUNT: U512 = U512::from(250_000_000) + 1000; + static ref TRANSFER_2_AMOUNT: U512 = U512::from(750); + static ref TRANSFER_2_AMOUNT_WITH_ADV: U512 = *DEFAULT_PAYMENT + *TRANSFER_2_AMOUNT; + static ref TRANSFER_TOO_MUCH: U512 = U512::from(u64::max_value()); + static ref ACCOUNT_1_INITIAL_BALANCE: U512 = *DEFAULT_PAYMENT; +} +#[ignore] +#[test] +fn should_call_group_restricted_session() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let _package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_2 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + RESTRICTED_SESSION, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_2).expect_success().commit(); + + let _account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); +} + +#[ignore] +#[test] +fn should_call_group_restricted_session_caller() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_2 = { + let args = runtime_args! { + PACKAGE_HASH_ARG => package_hash.into_hash(), + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + RESTRICTED_SESSION_CALLER, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + builder.exec(exec_request_2).expect_success().commit(); + + let _account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); +} + +#[test] +#[ignore] +fn should_not_call_restricted_session_from_wrong_account() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *TRANSFER_1_AMOUNT }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + builder.exec(exec_request_2).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_3 = { + let args = runtime_args! {}; + let deploy = DeployItemBuilder::new() + .with_address(ACCOUNT_1_ADDR) + .with_stored_versioned_contract_by_hash( + package_hash.into_hash().expect("should be hash"), + Some(CONTRACT_INITIAL_VERSION), + RESTRICTED_SESSION, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[ACCOUNT_1_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_3).commit(); + + let _account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let response = builder + .get_exec_responses() + .last() + .expect("should have last response"); + assert_eq!(response.len(), 1); + let exec_response = response.last().expect("should have response"); + let error = exec_response.as_error().expect("should have error"); + assert_matches!(error, Error::Exec(execution::Error::InvalidContext)); +} + +#[test] +#[ignore] +fn should_not_call_restricted_session_caller_from_wrong_account() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *TRANSFER_1_AMOUNT }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + builder.exec(exec_request_2).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_3 = { + let args = runtime_args! { + "package_hash" => *package_hash, + }; + let deploy = DeployItemBuilder::new() + .with_address(ACCOUNT_1_ADDR) + .with_stored_versioned_contract_by_hash( + package_hash.into_hash().expect("should be hash"), + Some(CONTRACT_INITIAL_VERSION), + RESTRICTED_SESSION_CALLER, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[ACCOUNT_1_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_3).commit(); + + let _account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let response = builder + .get_exec_responses() + .last() + .expect("should have last response"); + assert_eq!(response.len(), 1); + let exec_response = response.last().expect("should have response"); + let error = exec_response.as_error().expect("should have error"); + assert_matches!(error, Error::Exec(execution::Error::InvalidContext)); +} + +#[ignore] +#[test] +fn should_call_group_restricted_contract() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_2 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + PACKAGE_HASH_ARG => package_hash.into_hash(), + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + RESTRICTED_CONTRACT, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_2).expect_success().commit(); + + let _account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); +} + +#[ignore] +#[test] +fn should_not_call_group_restricted_contract_from_wrong_account() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *TRANSFER_1_AMOUNT }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + builder.exec(exec_request_2).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_3 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + PACKAGE_HASH_ARG => package_hash.into_hash(), + }; + let deploy = DeployItemBuilder::new() + .with_address(ACCOUNT_1_ADDR) + .with_stored_versioned_contract_by_hash( + package_hash.into_hash().expect("should be hash"), + Some(CONTRACT_INITIAL_VERSION), + RESTRICTED_CONTRACT, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[ACCOUNT_1_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_3).commit(); + + let response = builder + .get_exec_responses() + .last() + .expect("should have last response"); + assert_eq!(response.len(), 1); + let exec_response = response.last().expect("should have response"); + let error = exec_response.as_error().expect("should have error"); + assert_matches!(error, Error::Exec(execution::Error::InvalidContext)); +} + +#[ignore] +#[test] +fn should_call_group_unrestricted_contract_caller() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_2 = { + let args = runtime_args! { + PACKAGE_HASH_ARG => package_hash.into_hash(), + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + UNRESTRICTED_CONTRACT_CALLER, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + builder.exec(exec_request_2).expect_success().commit(); + + let _account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); +} + +#[ignore] +#[test] +fn should_call_unrestricted_contract_caller_from_different_account() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *TRANSFER_1_AMOUNT }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + builder.exec(exec_request_2).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_3 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + PACKAGE_HASH_ARG => package_hash.into_hash(), + }; + let deploy = DeployItemBuilder::new() + .with_address(ACCOUNT_1_ADDR) + .with_stored_versioned_contract_by_hash( + package_hash.into_hash().expect("should be hash"), + Some(CONTRACT_INITIAL_VERSION), + UNRESTRICTED_CONTRACT_CALLER, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[ACCOUNT_1_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_3).expect_success().commit(); +} + +#[ignore] +#[test] +fn should_call_group_restricted_contract_as_session() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *TRANSFER_1_AMOUNT }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + builder.exec(exec_request_2).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_3 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + PACKAGE_HASH_ARG => package_hash.into_hash(), + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_hash( + package_hash.into_hash().expect("should be hash"), + Some(CONTRACT_INITIAL_VERSION), + RESTRICTED_CONTRACT_CALLER_AS_SESSION, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([4; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_3).expect_success().commit(); +} + +#[ignore] +#[test] +fn should_call_group_restricted_contract_as_session_from_wrong_account() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *TRANSFER_1_AMOUNT }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + builder.exec(exec_request_2).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_3 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + PACKAGE_HASH_ARG => package_hash.into_hash(), + }; + let deploy = DeployItemBuilder::new() + .with_address(ACCOUNT_1_ADDR) + .with_stored_versioned_contract_by_hash( + package_hash.into_hash().expect("should be hash"), + Some(CONTRACT_INITIAL_VERSION), + RESTRICTED_CONTRACT_CALLER_AS_SESSION, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[ACCOUNT_1_ADDR]) + .with_deploy_hash([4; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_3).commit(); + + let response = builder + .get_exec_responses() + .last() + .expect("should have last response"); + assert_eq!(response.len(), 1); + let exec_response = response.last().expect("should have response"); + let error = exec_response.as_error().expect("should have error"); + assert_matches!(error, Error::Exec(execution::Error::InvalidContext)); +} + +#[ignore] +#[test] +fn should_not_call_uncallable_contract_from_deploy() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_2 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + PACKAGE_HASH_ARG => package_hash.into_hash(), + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + UNCALLABLE_SESSION, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_2).commit(); + let response = builder + .get_exec_responses() + .last() + .expect("should have last response"); + assert_eq!(response.len(), 1); + let exec_response = response.last().expect("should have response"); + let error = exec_response.as_error().expect("should have error"); + assert_matches!(error, Error::Exec(execution::Error::InvalidContext)); + + let exec_request_3 = { + let args = runtime_args! { + PACKAGE_HASH_ARG => package_hash.into_hash(), + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + CALL_RESTRICTED_ENTRY_POINTS, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([6; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_3).expect_success().commit(); +} + +#[ignore] +#[test] +fn should_not_call_uncallable_session_from_deploy() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_2 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + PACKAGE_HASH_ARG => package_hash.into_hash(), + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + UNCALLABLE_CONTRACT, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_2).commit(); + let response = builder + .get_exec_responses() + .last() + .expect("should have last response"); + assert_eq!(response.len(), 1); + let exec_response = response.last().expect("should have response"); + let error = exec_response.as_error().expect("should have error"); + assert_matches!(error, Error::Exec(execution::Error::InvalidContext)); + + let exec_request_3 = { + let args = runtime_args! { + PACKAGE_HASH_ARG => package_hash.into_hash(), + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + CALL_RESTRICTED_ENTRY_POINTS, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([6; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + builder.exec(exec_request_3).expect_success().commit(); +} diff --git a/grpc/tests/src/test/manage_groups.rs b/grpc/tests/src/test/manage_groups.rs new file mode 100644 index 0000000000..f08975903e --- /dev/null +++ b/grpc/tests/src/test/manage_groups.rs @@ -0,0 +1,509 @@ +use assert_matches::assert_matches; +use contracts::CONTRACT_INITIAL_VERSION; +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use lazy_static::lazy_static; +use node::contract_core::{engine_state::Error, execution}; +use std::{collections::BTreeSet, iter::FromIterator}; +use types::{contracts, contracts::MAX_GROUPS, runtime_args, Group, Key, RuntimeArgs}; + +const CONTRACT_GROUPS: &str = "manage_groups.wasm"; +const PACKAGE_HASH_KEY: &str = "package_hash_key"; +const PACKAGE_ACCESS_KEY: &str = "package_access_key"; +const CREATE_GROUP: &str = "create_group"; +const REMOVE_GROUP: &str = "remove_group"; +const EXTEND_GROUP_UREFS: &str = "extend_group_urefs"; +const REMOVE_GROUP_UREFS: &str = "remove_group_urefs"; +const GROUP_NAME_ARG: &str = "group_name"; +const UREFS_ARG: &str = "urefs"; +const NEW_UREFS_COUNT: u64 = 3; +const GROUP_1_NAME: &str = "Group 1"; +const TOTAL_NEW_UREFS_ARG: &str = "total_new_urefs"; +const TOTAL_EXISTING_UREFS_ARG: &str = "total_existing_urefs"; +const ARG_AMOUNT: &str = "amount"; + +lazy_static! { + static ref DEFAULT_CREATE_GROUP_ARGS: RuntimeArgs = runtime_args! { + GROUP_NAME_ARG => GROUP_1_NAME, + TOTAL_NEW_UREFS_ARG => 1u64, + TOTAL_EXISTING_UREFS_ARG => 1u64, + }; +} + +#[ignore] +#[test] +fn should_create_and_remove_group() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_2 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + CREATE_GROUP, + DEFAULT_CREATE_GROUP_ARGS.clone(), + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_2).expect_success().commit(); + + let query_result = builder + .query(None, *package_hash, &[]) + .expect("should have result"); + let contract_package = query_result + .as_contract_package() + .expect("should be package"); + assert_eq!(contract_package.groups().len(), 1); + let group_1 = contract_package + .groups() + .get(&Group::new(GROUP_1_NAME)) + .expect("should have group"); + assert_eq!(group_1.len(), 2); + + let exec_request_3 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + GROUP_NAME_ARG => GROUP_1_NAME, + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + REMOVE_GROUP, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_3).expect_success().commit(); + + let query_result = builder + .query(None, *package_hash, &[]) + .expect("should have result"); + let contract_package = query_result + .as_contract_package() + .expect("should be package"); + assert_eq!( + contract_package.groups().get(&Group::new(GROUP_1_NAME)), + None + ); +} + +#[ignore] +#[test] +fn should_create_and_extend_user_group() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_2 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + CREATE_GROUP, + DEFAULT_CREATE_GROUP_ARGS.clone(), + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([5; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_2).expect_success().commit(); + + let query_result = builder + .query(None, *package_hash, &[]) + .expect("should have result"); + let contract_package = query_result + .as_contract_package() + .expect("should be package"); + assert_eq!(contract_package.groups().len(), 1); + let group_1 = contract_package + .groups() + .get(&Group::new(GROUP_1_NAME)) + .expect("should have group"); + assert_eq!(group_1.len(), 2); + + let exec_request_3 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + GROUP_NAME_ARG => GROUP_1_NAME, + TOTAL_NEW_UREFS_ARG => NEW_UREFS_COUNT, + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + EXTEND_GROUP_UREFS, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_3).expect_success().commit(); + + let query_result = builder + .query(None, *package_hash, &[]) + .expect("should have result"); + let contract_package = query_result + .as_contract_package() + .expect("should be package"); + let group_1_extended = contract_package + .groups() + .get(&Group::new(GROUP_1_NAME)) + .expect("should have group"); + assert!(group_1_extended.len() > group_1.len()); + // Calculates how many new urefs were created + let new_urefs = BTreeSet::from_iter(group_1_extended.difference(&group_1)); + assert_eq!(new_urefs.len(), NEW_UREFS_COUNT as usize); +} + +#[ignore] +#[test] +fn should_create_and_remove_urefs_from_group() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_2 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + CREATE_GROUP, + DEFAULT_CREATE_GROUP_ARGS.clone(), + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_2).expect_success().commit(); + + let query_result = builder + .query(None, *package_hash, &[]) + .expect("should have result"); + let contract_package = query_result + .as_contract_package() + .expect("should be package"); + assert_eq!(contract_package.groups().len(), 1); + let group_1 = contract_package + .groups() + .get(&Group::new(GROUP_1_NAME)) + .expect("should have group"); + assert_eq!(group_1.len(), 2); + + let urefs_to_remove = Vec::from_iter(group_1.to_owned()); + + let exec_request_3 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + GROUP_NAME_ARG => GROUP_1_NAME, + UREFS_ARG => urefs_to_remove, + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + REMOVE_GROUP_UREFS, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_3).expect_success().commit(); + + let query_result = builder + .query(None, *package_hash, &[]) + .expect("should have result"); + let contract_package = query_result + .as_contract_package() + .expect("should be package"); + let group_1_modified = contract_package + .groups() + .get(&Group::new(GROUP_1_NAME)) + .expect("should have group 1"); + assert!(group_1_modified.len() < group_1.len()); +} + +#[ignore] +#[test] +fn should_limit_max_urefs_while_extending() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_GROUPS, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + let account = builder + .query(None, Key::Account(DEFAULT_ACCOUNT_ADDR), &[]) + .expect("should query account") + .as_account() + .cloned() + .expect("should be account"); + + let package_hash = account + .named_keys() + .get(PACKAGE_HASH_KEY) + .expect("should have contract package"); + let _access_uref = account + .named_keys() + .get(PACKAGE_ACCESS_KEY) + .expect("should have package hash"); + + let exec_request_2 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + CREATE_GROUP, + DEFAULT_CREATE_GROUP_ARGS.clone(), + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([3; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_2).expect_success().commit(); + + let query_result = builder + .query(None, *package_hash, &[]) + .expect("should have result"); + let contract_package = query_result + .as_contract_package() + .expect("should be package"); + assert_eq!(contract_package.groups().len(), 1); + let group_1 = contract_package + .groups() + .get(&Group::new(GROUP_1_NAME)) + .expect("should have group"); + assert_eq!(group_1.len(), 2); + + let exec_request_3 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + GROUP_NAME_ARG => GROUP_1_NAME, + TOTAL_NEW_UREFS_ARG => 8u64, + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + EXTEND_GROUP_UREFS, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([5; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let exec_request_4 = { + // This inserts package as an argument because this test + // can work from different accounts which might not have the same keys in their session + // code. + let args = runtime_args! { + GROUP_NAME_ARG => GROUP_1_NAME, + // Exceeds by 1 + TOTAL_NEW_UREFS_ARG => 1u64, + }; + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_stored_versioned_contract_by_name( + PACKAGE_HASH_KEY, + Some(CONTRACT_INITIAL_VERSION), + EXTEND_GROUP_UREFS, + args, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([32; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request_3).expect_success().commit(); + + let query_result = builder + .query(None, *package_hash, &[]) + .expect("should have result"); + let contract_package = query_result + .as_contract_package() + .expect("should be package"); + let group_1_modified = contract_package + .groups() + .get(&Group::new(GROUP_1_NAME)) + .expect("should have group 1"); + assert_eq!(group_1_modified.len(), MAX_GROUPS as usize); + + // Tries to exceed the limit by 1 + builder.exec(exec_request_4).commit(); + + let response = builder + .get_exec_responses() + .last() + .expect("should have last response"); + assert_eq!(response.len(), 1); + let exec_response = response.last().expect("should have response"); + let error = exec_response.as_error().expect("should have error"); + let error = assert_matches!(error, Error::Exec(execution::Error::Revert(e)) => e); + assert_eq!(error, &contracts::Error::MaxTotalURefsExceeded.into()); +} diff --git a/grpc/tests/src/test/mod.rs b/grpc/tests/src/test/mod.rs new file mode 100644 index 0000000000..3e3ae3cb3a --- /dev/null +++ b/grpc/tests/src/test/mod.rs @@ -0,0 +1,12 @@ +mod check_transfer_success; +mod contract_api; +mod contract_context; +mod counter; +mod deploy; +mod explorer; +mod groups; +mod manage_groups; +mod regression; +mod system_contracts; +mod upgrade; +mod wasmless_transfer; diff --git a/grpc/tests/src/test/regression/ee_221.rs b/grpc/tests/src/test/regression/ee_221.rs new file mode 100644 index 0000000000..e776f1933d --- /dev/null +++ b/grpc/tests/src/test/regression/ee_221.rs @@ -0,0 +1,27 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::RuntimeArgs; + +const CONTRACT_EE_221_REGRESSION: &str = "ee_221_regression.wasm"; + +#[ignore] +#[test] +fn should_run_ee_221_get_uref_regression_test() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_221_REGRESSION, + RuntimeArgs::default(), + ) + .build(); + + let _result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .expect_success() + .commit() + .finish(); +} diff --git a/grpc/tests/src/test/regression/ee_401.rs b/grpc/tests/src/test/regression/ee_401.rs new file mode 100644 index 0000000000..41ff0a4471 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_401.rs @@ -0,0 +1,32 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::RuntimeArgs; + +const CONTRACT_EE_401_REGRESSION: &str = "ee_401_regression.wasm"; +const CONTRACT_EE_401_REGRESSION_CALL: &str = "ee_401_regression_call.wasm"; + +#[ignore] +#[test] +fn should_execute_contracts_which_provide_extra_urefs() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_401_REGRESSION, + RuntimeArgs::default(), + ) + .build(); + + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_401_REGRESSION_CALL, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + builder.exec(exec_request_2).expect_success().commit(); +} diff --git a/grpc/tests/src/test/regression/ee_441.rs b/grpc/tests/src/test/regression/ee_441.rs new file mode 100644 index 0000000000..80ae45fb29 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_441.rs @@ -0,0 +1,74 @@ +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, ARG_AMOUNT, + DEFAULT_PAYMENT, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{runtime_args, Key, RuntimeArgs, URef}; + +const EE_441_RNG_STATE: &str = "ee_441_rng_state.wasm"; + +fn get_uref(key: Key) -> URef { + match key { + Key::URef(uref) => uref, + _ => panic!("Key {:?} is not an URef", key), + } +} + +fn do_pass(pass: &str) -> (URef, URef) { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_session_code( + EE_441_RNG_STATE, + runtime_args! { + "flag" => pass, + }, + ) + .with_deploy_hash([1u8; 32]) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .build(); + ExecuteRequestBuilder::from_deploy_item(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .expect_success() + .commit(); + + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + ( + get_uref(account.named_keys()["uref1"]), + get_uref(account.named_keys()["uref2"]), + ) +} + +#[ignore] +#[test] +fn should_properly_pass_rng_state_to_subcontracts() { + // the baseline pass, no subcalls + let (pass1_uref1, pass1_uref2) = do_pass("pass1"); + // second pass does a subcall that does nothing, should be consistent with pass1 + let (pass2_uref1, pass2_uref2) = do_pass("pass2"); + // second pass calls new_uref, and uref2 is returned from a sub call + let (pass3_uref1, pass3_uref2) = do_pass("pass3"); + + // First urefs from each pass should yield same results where pass1 is the + // baseline + assert_eq!(pass1_uref1.addr(), pass2_uref1.addr()); + assert_eq!(pass2_uref1.addr(), pass3_uref1.addr()); + + // Second urefs from each pass should yield the same result where pass1 is the + // baseline + assert_eq!(pass1_uref2.addr(), pass2_uref2.addr()); + assert_eq!(pass2_uref2.addr(), pass3_uref2.addr()); +} diff --git a/grpc/tests/src/test/regression/ee_460.rs b/grpc/tests/src/test/regression/ee_460.rs new file mode 100644 index 0000000000..75bc6cca52 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_460.rs @@ -0,0 +1,41 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_shared::transform::Transform; +use types::{runtime_args, RuntimeArgs, U512}; + +const CONTRACT_EE_460_REGRESSION: &str = "ee_460_regression.wasm"; + +const ARG_AMOUNT: &str = "amount"; + +#[ignore] +#[test] +fn should_run_ee_460_no_side_effects_on_error_regression() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_460_REGRESSION, + runtime_args! { ARG_AMOUNT => U512::max_value() }, + ) + .build(); + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .finish(); + + // In this regression test it is verified that no new urefs are created on the + // mint uref, which should mean no new purses are created in case of + // transfer error. This is considered sufficient cause to confirm that the + // mint uref is left untouched. + let mint_contract_uref = result.builder().get_mint_contract_hash(); + + let transforms = &result.builder().get_transforms()[0]; + let mint_transforms = transforms + .get(&mint_contract_uref.into()) + // Skips the Identity writes introduced since payment code execution for brevity of the + // check + .filter(|&v| v != &Transform::Identity); + assert!(mint_transforms.is_none()); +} diff --git a/grpc/tests/src/test/regression/ee_468.rs b/grpc/tests/src/test/regression/ee_468.rs new file mode 100644 index 0000000000..a6b48cf8dc --- /dev/null +++ b/grpc/tests/src/test/regression/ee_468.rs @@ -0,0 +1,25 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::RuntimeArgs; + +const CONTRACT_DESERIALIZE_ERROR: &str = "deserialize_error.wasm"; + +#[ignore] +#[test] +fn should_not_fail_deserializing() { + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_DESERIALIZE_ERROR, + RuntimeArgs::new(), + ) + .build(); + let is_error = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .is_error(); + + assert!(is_error); +} diff --git a/grpc/tests/src/test/regression/ee_470.rs b/grpc/tests/src/test/regression/ee_470.rs new file mode 100644 index 0000000000..7ffd692d9f --- /dev/null +++ b/grpc/tests/src/test/regression/ee_470.rs @@ -0,0 +1,64 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_storage::global_state::in_memory::InMemoryGlobalState; +use types::RuntimeArgs; + +const CONTRACT_DO_NOTHING: &str = "do_nothing.wasm"; + +#[ignore] +#[test] +fn regression_test_genesis_hash_mismatch() { + let mut builder_base = InMemoryWasmTestBuilder::default(); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_DO_NOTHING, + RuntimeArgs::default(), + ) + .build(); + + // Step 1. + let builder = builder_base.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + // This is trie's post state hash after calling run_genesis endpoint. + // Step 1a) + let genesis_run_hash = builder.get_genesis_hash(); + let genesis_transforms = builder.get_genesis_transforms().clone(); + + let empty_root_hash = { + let gs = InMemoryGlobalState::empty().expect("Empty GlobalState."); + gs.empty_root_hash + }; + + // This is trie's post state hash after committing genesis effects on top of + // empty trie. Step 1b) + let genesis_transforms_hash = builder + .commit_effects(empty_root_hash.to_vec(), genesis_transforms) + .get_post_state_hash(); + + // They should match. + assert_eq!(genesis_run_hash, genesis_transforms_hash); + + // Step 2. + builder.exec(exec_request_1).commit().expect_success(); + + // No step 3. + // Step 4. + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + // Step 4a) + let second_genesis_run_hash = builder.get_genesis_hash(); + let second_genesis_transforms = builder.get_genesis_transforms().clone(); + + // Step 4b) + let second_genesis_transforms_hash = builder + .commit_effects(empty_root_hash.to_vec(), second_genesis_transforms) + .get_post_state_hash(); + + assert_eq!(second_genesis_run_hash, second_genesis_transforms_hash); + + assert_eq!(second_genesis_run_hash, genesis_run_hash); + assert_eq!(second_genesis_transforms_hash, genesis_transforms_hash); +} diff --git a/grpc/tests/src/test/regression/ee_532.rs b/grpc/tests/src/test/regression/ee_532.rs new file mode 100644 index 0000000000..0de1c02be1 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_532.rs @@ -0,0 +1,47 @@ +use engine_test_support::internal::{ + ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST, +}; +use node::contract_core::engine_state::Error; +use types::{account::AccountHash, RuntimeArgs}; + +const CONTRACT_EE_532_REGRESSION: &str = "ee_532_regression.wasm"; +const UNKNOWN_ADDR: AccountHash = AccountHash::new([42u8; 32]); + +#[ignore] +#[test] +fn should_run_ee_532_get_uref_regression_test() { + // This test runs a contract that's after every call extends the same key with + // more data + + let exec_request = ExecuteRequestBuilder::standard( + UNKNOWN_ADDR, + CONTRACT_EE_532_REGRESSION, + RuntimeArgs::default(), + ) + .build(); + + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + let deploy_result = result + .builder() + .get_exec_response(0) + .expect("should have exec response") + .get(0) + .expect("should have at least one deploy result"); + + assert!( + deploy_result.has_precondition_failure(), + "expected precondition failure" + ); + + let message = deploy_result.as_error().map(|err| format!("{}", err)); + assert_eq!( + message, + Some(format!("{}", Error::Authorization)), + "expected Error::Authorization" + ) +} diff --git a/grpc/tests/src/test/regression/ee_536.rs b/grpc/tests/src/test/regression/ee_536.rs new file mode 100644 index 0000000000..de9ce73788 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_536.rs @@ -0,0 +1,27 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::RuntimeArgs; + +const CONTRACT_EE_536_REGRESSION: &str = "ee_536_regression.wasm"; + +#[ignore] +#[test] +fn should_run_ee_536_get_uref_regression_test() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_536_REGRESSION, + RuntimeArgs::default(), + ) + .build(); + + let _result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .expect_success() + .commit() + .finish(); +} diff --git a/grpc/tests/src/test/regression/ee_539.rs b/grpc/tests/src/test/regression/ee_539.rs new file mode 100644 index 0000000000..73bfd57616 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_539.rs @@ -0,0 +1,29 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{account::Weight, runtime_args, RuntimeArgs}; + +const CONTRACT_EE_539_REGRESSION: &str = "ee_539_regression.wasm"; +const ARG_KEY_MANAGEMENT_THRESHOLD: &str = "key_management_threshold"; +const ARG_DEPLOYMENT_THRESHOLD: &str = "deployment_threshold"; + +#[ignore] +#[test] +fn should_run_ee_539_serialize_action_thresholds_regression() { + // This test runs a contract that's after every call extends the same key with + // more data + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_539_REGRESSION, + runtime_args! { ARG_KEY_MANAGEMENT_THRESHOLD => Weight::new(4), ARG_DEPLOYMENT_THRESHOLD => Weight::new(3) }, + ) + .build(); + + let _result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .expect_success() + .commit() + .finish(); +} diff --git a/grpc/tests/src/test/regression/ee_549.rs b/grpc/tests/src/test/regression/ee_549.rs new file mode 100644 index 0000000000..85875440af --- /dev/null +++ b/grpc/tests/src/test/regression/ee_549.rs @@ -0,0 +1,28 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::RuntimeArgs; + +const CONTRACT_EE_549_REGRESSION: &str = "ee_549_regression.wasm"; + +#[ignore] +#[test] +fn should_run_ee_549_set_refund_regression() { + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_549_REGRESSION, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request); + + // Execution should encounter an error because set_refund + // is not allowed to be called during session execution. + assert!(builder.is_error()); +} diff --git a/grpc/tests/src/test/regression/ee_550.rs b/grpc/tests/src/test/regression/ee_550.rs new file mode 100644 index 0000000000..ed71b85365 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_550.rs @@ -0,0 +1,92 @@ +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, ARG_AMOUNT, + DEFAULT_PAYMENT, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{account::AccountHash, runtime_args, RuntimeArgs}; + +const PASS_INIT_REMOVE: &str = "init_remove"; +const PASS_TEST_REMOVE: &str = "test_remove"; +const PASS_INIT_UPDATE: &str = "init_update"; +const PASS_TEST_UPDATE: &str = "test_update"; + +const CONTRACT_EE_550_REGRESSION: &str = "ee_550_regression.wasm"; +const KEY_2_ADDR: [u8; 32] = [101; 32]; +const DEPLOY_HASH: [u8; 32] = [42; 32]; +const ARG_PASS: &str = "pass"; + +#[ignore] +#[test] +fn should_run_ee_550_remove_with_saturated_threshold_regression() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_550_REGRESSION, + runtime_args! { ARG_PASS => String::from(PASS_INIT_REMOVE) }, + ) + .build(); + + let exec_request_2 = { + let deploy_item = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code( + CONTRACT_EE_550_REGRESSION, + runtime_args! { ARG_PASS => String::from(PASS_TEST_REMOVE) }, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR, AccountHash::new(KEY_2_ADDR)]) + .with_deploy_hash(DEPLOY_HASH) + .build(); + + ExecuteRequestBuilder::from_deploy_item(deploy_item).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .expect_success() + .commit(); +} + +#[ignore] +#[test] +fn should_run_ee_550_update_with_saturated_threshold_regression() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_550_REGRESSION, + runtime_args! { ARG_PASS => String::from(PASS_INIT_UPDATE) }, + ) + .build(); + + let exec_request_2 = { + let deploy_item = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code( + CONTRACT_EE_550_REGRESSION, + runtime_args! { ARG_PASS => String::from(PASS_TEST_UPDATE) }, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR, AccountHash::new(KEY_2_ADDR)]) + .with_deploy_hash(DEPLOY_HASH) + .build(); + + ExecuteRequestBuilder::from_deploy_item(deploy_item).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .expect_success() + .commit(); +} diff --git a/grpc/tests/src/test/regression/ee_572.rs b/grpc/tests/src/test/regression/ee_572.rs new file mode 100644 index 0000000000..2da285aae7 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_572.rs @@ -0,0 +1,95 @@ +use engine_test_support::{ + internal::{ + utils, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_shared::stored_value::StoredValue; +use types::{account::AccountHash, runtime_args, Key, RuntimeArgs, U512}; + +const CONTRACT_CREATE: &str = "ee_572_regression_create.wasm"; +const CONTRACT_ESCALATE: &str = "ee_572_regression_escalate.wasm"; +const CONTRACT_TRANSFER: &str = "transfer_purse_to_account.wasm"; +const CREATE: &str = "create"; + +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const ACCOUNT_2_ADDR: AccountHash = AccountHash::new([2u8; 32]); + +#[ignore] +#[test] +fn should_run_ee_572_regression() { + let account_amount: U512 = *DEFAULT_PAYMENT + U512::from(100); + let account_1_creation_args = runtime_args! { + "target" => ACCOUNT_1_ADDR, + "amount" => account_amount + }; + let account_2_creation_args = runtime_args! { + "target" => ACCOUNT_2_ADDR, + "amount" => account_amount, + }; + + // This test runs a contract that's after every call extends the same key with + // more data + let mut builder = InMemoryWasmTestBuilder::default(); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER, + account_1_creation_args, + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER, + account_2_creation_args.clone(), + ) + .build(); + + let exec_request_3 = + ExecuteRequestBuilder::standard(ACCOUNT_1_ADDR, CONTRACT_CREATE, account_2_creation_args) + .build(); + + // Create Accounts + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit(); + + builder.exec(exec_request_2).expect_success().commit(); + + // Store the creation contract + builder.exec(exec_request_3).expect_success().commit(); + + let contract: Key = { + let account = match builder.query(None, Key::Account(ACCOUNT_1_ADDR), &[]) { + Ok(StoredValue::Account(account)) => account, + _ => panic!("Could not find account at: {:?}", ACCOUNT_1_ADDR), + }; + *account + .named_keys() + .get(CREATE) + .expect("Could not find contract pointer") + }; + + let exec_request_4 = ExecuteRequestBuilder::standard( + ACCOUNT_2_ADDR, + CONTRACT_ESCALATE, + runtime_args! { + "contract_hash" => contract.into_hash().expect("should be hash"), + }, + ) + .build(); + + // Attempt to forge a new URef with escalated privileges + let response = builder + .exec(exec_request_4) + .get_exec_response(3) + .expect("should have a response") + .to_owned(); + + let error_message = utils::get_error_message(response); + + assert!(error_message.contains("ForgedReference"), error_message); +} diff --git a/grpc/tests/src/test/regression/ee_584.rs b/grpc/tests/src/test/regression/ee_584.rs new file mode 100644 index 0000000000..1aa823d78e --- /dev/null +++ b/grpc/tests/src/test/regression/ee_584.rs @@ -0,0 +1,40 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use types::RuntimeArgs; + +const CONTRACT_EE_584_REGRESSION: &str = "ee_584_regression.wasm"; + +#[ignore] +#[test] +fn should_run_ee_584_no_errored_session_transforms() { + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_584_REGRESSION, + RuntimeArgs::default(), + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request); + + assert!(builder.is_error()); + + let transforms = builder.get_transforms(); + + assert!(transforms[0] + .iter() + .find( + |(_, t)| if let Transform::Write(StoredValue::CLValue(cl_value)) = t { + cl_value.to_owned().into_t::().unwrap_or_default() == "Hello, World!" + } else { + false + } + ) + .is_none()); +} diff --git a/grpc/tests/src/test/regression/ee_597.rs b/grpc/tests/src/test/regression/ee_597.rs new file mode 100644 index 0000000000..9cdd26b338 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_597.rs @@ -0,0 +1,44 @@ +use engine_test_support::{ + internal::{ + utils, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{ApiError, RuntimeArgs}; + +const CONTRACT_EE_597_REGRESSION: &str = "ee_597_regression.wasm"; + +#[ignore] +#[test] +fn should_fail_when_bonding_amount_is_zero_ee_597_regression() { + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_597_REGRESSION, + RuntimeArgs::default(), + ) + .build(); + + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + let response = result + .builder() + .get_exec_response(0) + .expect("should have a response") + .to_owned(); + + let error_message = utils::get_error_message(response); + + if !cfg!(feature = "enable-bonding") { + assert!(error_message.contains(&format!("{:?}", ApiError::Unhandled))); + } else { + // Error::BondTooSmall => 5, + assert!( + error_message.contains(&format!("{:?}", ApiError::ProofOfStake(5))), + error_message + ); + } +} diff --git a/grpc/tests/src/test/regression/ee_598.rs b/grpc/tests/src/test/regression/ee_598.rs new file mode 100644 index 0000000000..3a120ffc20 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_598.rs @@ -0,0 +1,94 @@ +use lazy_static::lazy_static; + +use engine_test_support::{ + internal::{ + utils, DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNTS, + DEFAULT_PAYMENT, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::genesis::GenesisAccount; +use node::contract_shared::motes::Motes; +use types::{account::AccountHash, runtime_args, ApiError, RuntimeArgs, U512}; + +const ARG_AMOUNT: &str = "amount"; +const ARG_ENTRY_POINT: &str = "entry_point"; +const ARG_ACCOUNT_PK: &str = "account_hash"; + +const CONTRACT_POS_BONDING: &str = "pos_bonding.wasm"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([7u8; 32]); + +const GENESIS_VALIDATOR_STAKE: u64 = 50_000; +lazy_static! { + static ref ACCOUNT_1_FUND: U512 = *DEFAULT_PAYMENT; + static ref ACCOUNT_1_BALANCE: U512 = *ACCOUNT_1_FUND + 100_000; + static ref ACCOUNT_1_BOND: U512 = 25_000.into(); +} + +#[ignore] +#[test] +fn should_fail_unboding_more_than_it_was_staked_ee_598_regression() { + let accounts = { + let mut tmp: Vec = DEFAULT_ACCOUNTS.clone(); + let account = GenesisAccount::new( + AccountHash::new([42; 32]), + Motes::new(GENESIS_VALIDATOR_STAKE.into()) * Motes::new(2.into()), + Motes::new(GENESIS_VALIDATOR_STAKE.into()), + ); + tmp.push(account); + tmp + }; + + let run_genesis_request = utils::create_run_genesis_request(accounts); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_POS_BONDING, + runtime_args! { + ARG_ENTRY_POINT => "seed_new_account", + ARG_ACCOUNT_PK => ACCOUNT_1_ADDR, + ARG_AMOUNT => *ACCOUNT_1_BALANCE, + }, + ) + .build(); + let exec_request_2 = { + let deploy = DeployItemBuilder::new() + .with_address(ACCOUNT_1_ADDR) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *ACCOUNT_1_FUND }) + .with_session_code( + "ee_598_regression.wasm", + runtime_args! { ARG_AMOUNT => *ACCOUNT_1_BOND }, + ) + .with_deploy_hash([2u8; 32]) + .with_authorization_keys(&[ACCOUNT_1_ADDR]) + .build(); + ExecuteRequestBuilder::from_deploy_item(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + builder.run_genesis(&run_genesis_request); + + builder.exec(exec_request_1).expect_success().commit(); + + let result = builder.exec(exec_request_2).commit().finish(); + + let response = result + .builder() + .get_exec_response(1) + .expect("should have a response") + .to_owned(); + let error_message = utils::get_error_message(response); + + if !cfg!(feature = "enable-bonding") { + assert!( + error_message.contains(&format!("{:?}", ApiError::Unhandled)), + error_message + ); + } else { + // Error::UnbondTooLarge => 7, + assert!( + error_message.contains(&format!("{:?}", ApiError::ProofOfStake(7))), + error_message + ); + } +} diff --git a/grpc/tests/src/test/regression/ee_599.rs b/grpc/tests/src/test/regression/ee_599.rs new file mode 100644 index 0000000000..ed2ed881ad --- /dev/null +++ b/grpc/tests/src/test/regression/ee_599.rs @@ -0,0 +1,349 @@ +use lazy_static::lazy_static; + +use engine_test_support::{ + internal::{ + utils, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::CONV_RATE; +use node::contract_shared::motes::Motes; +use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; + +const CONTRACT_EE_599_REGRESSION: &str = "ee_599_regression.wasm"; +const CONTRACT_TRANSFER_TO_ACCOUNT: &str = "transfer_to_account_u512.wasm"; +const DONATION_PURSE_COPY_KEY: &str = "donation_purse_copy"; +const EXPECTED_ERROR: &str = "InvalidContext"; +const TRANSFER_FUNDS_KEY: &str = "transfer_funds"; +const VICTIM_ADDR: AccountHash = AccountHash::new([42; 32]); + +lazy_static! { + static ref VICTIM_INITIAL_FUNDS: U512 = *DEFAULT_PAYMENT * 10; +} + +fn setup() -> InMemoryWasmTestBuilder { + // Creates victim account + let exec_request_1 = { + let args = runtime_args! { + "target" => VICTIM_ADDR, + "amount" => *VICTIM_INITIAL_FUNDS, + }; + ExecuteRequestBuilder::standard(DEFAULT_ACCOUNT_ADDR, CONTRACT_TRANSFER_TO_ACCOUNT, args) + .build() + }; + + // Deploy contract + let exec_request_2 = { + let args = runtime_args! { + "method" => "install".to_string(), + }; + ExecuteRequestBuilder::standard(DEFAULT_ACCOUNT_ADDR, CONTRACT_EE_599_REGRESSION, args) + .build() + }; + + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .expect_success() + .commit() + .finish(); + + InMemoryWasmTestBuilder::from_result(result) +} + +#[ignore] +#[test] +fn should_not_be_able_to_transfer_funds_with_transfer_purse_to_purse() { + let mut builder = setup(); + + let victim_account = builder + .get_account(VICTIM_ADDR) + .expect("should have victim account"); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have default account"); + let transfer_funds = default_account + .named_keys() + .get(TRANSFER_FUNDS_KEY) + .cloned() + .unwrap_or_else(|| panic!("should have {}", TRANSFER_FUNDS_KEY)); + let donation_purse_copy_key = default_account + .named_keys() + .get(DONATION_PURSE_COPY_KEY) + .cloned() + .unwrap_or_else(|| panic!("should have {}", DONATION_PURSE_COPY_KEY)); + + let donation_purse_copy = donation_purse_copy_key.into_uref().expect("should be uref"); + + let exec_request_3 = { + let args = runtime_args! { + "method" => "call", + "contract_key" => transfer_funds.into_hash().expect("should be hash"), + "sub_contract_method_fwd" => "transfer_from_purse_to_purse_ext", + }; + ExecuteRequestBuilder::standard(VICTIM_ADDR, CONTRACT_EE_599_REGRESSION, args).build() + }; + + let result_2 = builder.exec(exec_request_3).commit().finish(); + + let exec_3_response = result_2 + .builder() + .get_exec_response(0) + .expect("should have response"); + let gas_cost = Motes::from_gas(utils::get_exec_costs(exec_3_response)[0], CONV_RATE) + .expect("should convert"); + + let error_msg = result_2 + .builder() + .exec_error_message(0) + .expect("should have error"); + assert!( + error_msg.contains(EXPECTED_ERROR), + "Got error: {}", + error_msg + ); + + let victim_balance_after = result_2 + .builder() + .get_purse_balance(victim_account.main_purse()); + + assert_eq!( + *VICTIM_INITIAL_FUNDS - gas_cost.value(), + victim_balance_after + ); + + assert_eq!( + result_2.builder().get_purse_balance(donation_purse_copy), + U512::zero(), + ); +} + +#[ignore] +#[test] +fn should_not_be_able_to_transfer_funds_with_transfer_from_purse_to_account() { + let mut builder = setup(); + + let victim_account = builder + .get_account(VICTIM_ADDR) + .expect("should have victim account"); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have default account"); + + let default_account_balance = builder.get_purse_balance(default_account.main_purse()); + + let transfer_funds = default_account + .named_keys() + .get(TRANSFER_FUNDS_KEY) + .cloned() + .unwrap_or_else(|| panic!("should have {}", TRANSFER_FUNDS_KEY)); + let donation_purse_copy_key = default_account + .named_keys() + .get(DONATION_PURSE_COPY_KEY) + .cloned() + .unwrap_or_else(|| panic!("should have {}", DONATION_PURSE_COPY_KEY)); + + let donation_purse_copy = donation_purse_copy_key.into_uref().expect("should be uref"); + + let exec_request_3 = { + let args = runtime_args! { + "method" => "call".to_string(), + "contract_key" => transfer_funds.into_hash().expect("should get key"), + "sub_contract_method_fwd" => "transfer_from_purse_to_account_ext", + }; + ExecuteRequestBuilder::standard(VICTIM_ADDR, CONTRACT_EE_599_REGRESSION, args).build() + }; + + let result_2 = builder.exec(exec_request_3).commit().finish(); + + let exec_3_response = result_2 + .builder() + .get_exec_response(0) + .expect("should have response"); + + let gas_cost = Motes::from_gas(utils::get_exec_costs(exec_3_response)[0], CONV_RATE) + .expect("should convert"); + + let error_msg = result_2 + .builder() + .exec_error_message(0) + .expect("should have error"); + assert!( + error_msg.contains(EXPECTED_ERROR), + "Got error: {}", + error_msg + ); + + let victim_balance_after = result_2 + .builder() + .get_purse_balance(victim_account.main_purse()); + + assert_eq!( + *VICTIM_INITIAL_FUNDS - gas_cost.value(), + victim_balance_after + ); + // In this variant of test `donation_purse` is left unchanged i.e. zero balance + assert_eq!( + result_2.builder().get_purse_balance(donation_purse_copy), + U512::zero(), + ); + + // Main purse of the contract owner is unchanged + let updated_default_account_balance = result_2 + .builder() + .get_purse_balance(default_account.main_purse()); + + assert_eq!( + updated_default_account_balance - default_account_balance, + U512::zero(), + ) +} + +#[ignore] +#[test] +fn should_not_be_able_to_transfer_funds_with_transfer_to_account() { + let mut builder = setup(); + + let victim_account = builder + .get_account(VICTIM_ADDR) + .expect("should have victim account"); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have default account"); + + let default_account_balance = builder.get_purse_balance(default_account.main_purse()); + + let transfer_funds = default_account + .named_keys() + .get(TRANSFER_FUNDS_KEY) + .cloned() + .unwrap_or_else(|| panic!("should have {}", TRANSFER_FUNDS_KEY)); + let donation_purse_copy_key = default_account + .named_keys() + .get(DONATION_PURSE_COPY_KEY) + .cloned() + .unwrap_or_else(|| panic!("should have {}", DONATION_PURSE_COPY_KEY)); + + let donation_purse_copy = donation_purse_copy_key.into_uref().expect("should be uref"); + + let exec_request_3 = { + let args = runtime_args! { + "method" => "call", + "contract_key" => transfer_funds.into_hash().expect("should be hash"), + "sub_contract_method_fwd" => "transfer_to_account_ext", + }; + ExecuteRequestBuilder::standard(VICTIM_ADDR, CONTRACT_EE_599_REGRESSION, args).build() + }; + + let result_2 = builder.exec(exec_request_3).commit().finish(); + + let exec_3_response = result_2 + .builder() + .get_exec_response(0) + .expect("should have response"); + + let gas_cost = Motes::from_gas(utils::get_exec_costs(exec_3_response)[0], CONV_RATE) + .expect("should convert"); + + let error_msg = result_2 + .builder() + .exec_error_message(0) + .expect("should have error"); + assert!( + error_msg.contains(EXPECTED_ERROR), + "Got error: {}", + error_msg + ); + + let victim_balance_after = result_2 + .builder() + .get_purse_balance(victim_account.main_purse()); + + assert_eq!( + *VICTIM_INITIAL_FUNDS - gas_cost.value(), + victim_balance_after + ); + + // In this variant of test `donation_purse` is left unchanged i.e. zero balance + assert_eq!( + result_2.builder().get_purse_balance(donation_purse_copy), + U512::zero(), + ); + + // Verify that default account's balance didn't change + let updated_default_account_balance = result_2 + .builder() + .get_purse_balance(default_account.main_purse()); + + assert_eq!( + updated_default_account_balance - default_account_balance, + U512::zero(), + ) +} + +#[ignore] +#[test] +fn should_not_be_able_to_get_main_purse_in_invalid_context() { + let mut builder = setup(); + + let victim_account = builder + .get_account(VICTIM_ADDR) + .expect("should have victim account"); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have default account"); + + let transfer_funds = default_account + .named_keys() + .get(TRANSFER_FUNDS_KEY) + .cloned() + .unwrap_or_else(|| panic!("should have {}", TRANSFER_FUNDS_KEY)); + + let exec_request_3 = { + let args = runtime_args! { + "method" => "call".to_string(), + "contract_key" => transfer_funds.into_hash().expect("should be hash"), + "sub_contract_method_fwd" => "transfer_to_account_ext", + }; + ExecuteRequestBuilder::standard(VICTIM_ADDR, CONTRACT_EE_599_REGRESSION, args).build() + }; + + let victim_balance_before = builder.get_purse_balance(victim_account.main_purse()); + + let result_2 = builder.exec(exec_request_3).commit().finish(); + + let exec_3_response = result_2 + .builder() + .get_exec_response(0) + .expect("should have response"); + + let gas_cost = Motes::from_gas(utils::get_exec_costs(exec_3_response)[0], CONV_RATE) + .expect("should convert"); + + let error_msg = result_2 + .builder() + .exec_error_message(0) + .expect("should have error"); + assert!( + error_msg.contains(EXPECTED_ERROR), + "Got error: {}", + error_msg + ); + + let victim_balance_after = result_2 + .builder() + .get_purse_balance(victim_account.main_purse()); + + assert_eq!( + victim_balance_before - gas_cost.value(), + victim_balance_after + ); +} diff --git a/grpc/tests/src/test/regression/ee_601.rs b/grpc/tests/src/test/regression/ee_601.rs new file mode 100644 index 0000000000..c782194baf --- /dev/null +++ b/grpc/tests/src/test/regression/ee_601.rs @@ -0,0 +1,87 @@ +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use types::{runtime_args, CLValue, Key, RuntimeArgs}; + +const ARG_AMOUNT: &str = "amount"; + +#[ignore] +#[test] +fn should_run_ee_601_pay_session_new_uref_collision() { + let genesis_account_hash = DEFAULT_ACCOUNT_ADDR; + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_deploy_hash([1; 32]) + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_payment_code( + "ee_601_regression.wasm", + runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT }, + ) + .with_session_code("ee_601_regression.wasm", RuntimeArgs::default()) + .with_authorization_keys(&[genesis_account_hash]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request); + + let transforms = builder.get_transforms(); + let transform = &transforms[0]; + + let add_keys = if let Some(Transform::AddKeys(keys)) = + transform.get(&Key::Account(DEFAULT_ACCOUNT_ADDR)) + { + keys + } else { + panic!( + "expected AddKeys transform for given key but received {:?}", + transforms[0] + ); + }; + + let pay_uref = add_keys + .get("new_uref_result-payment") + .expect("payment uref should exist"); + + let session_uref = add_keys + .get("new_uref_result-session") + .expect("session uref should exist"); + + assert_ne!( + pay_uref, session_uref, + "payment and session code should not create same uref" + ); + + builder.commit(); + + let payment_value: StoredValue = builder + .query(None, *pay_uref, &[]) + .expect("should find payment value"); + + assert_eq!( + payment_value, + StoredValue::CLValue(CLValue::from_t("payment".to_string()).unwrap()), + "expected payment" + ); + + let session_value: StoredValue = builder + .query(None, *session_uref, &[]) + .expect("should find session value"); + + assert_eq!( + session_value, + StoredValue::CLValue(CLValue::from_t("session".to_string()).unwrap()), + "expected session" + ); +} diff --git a/grpc/tests/src/test/regression/ee_771.rs b/grpc/tests/src/test/regression/ee_771.rs new file mode 100644 index 0000000000..421e762ad7 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_771.rs @@ -0,0 +1,36 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use types::RuntimeArgs; + +const CONTRACT_EE_771_REGRESSION: &str = "ee_771_regression.wasm"; + +#[ignore] +#[test] +fn should_run_ee_771_regression() { + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_771_REGRESSION, + RuntimeArgs::default(), + ) + .build(); + + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + let response = result + .builder() + .get_exec_response(0) + .expect("should have a response") + .to_owned(); + + let error = response[0].as_error().expect("should have error"); + assert_eq!( + format!("{}", error), + "Function not found: functiondoesnotexist" + ); +} diff --git a/grpc/tests/src/test/regression/ee_803.rs b/grpc/tests/src/test/regression/ee_803.rs new file mode 100644 index 0000000000..f2df15324f --- /dev/null +++ b/grpc/tests/src/test/regression/ee_803.rs @@ -0,0 +1,155 @@ +use std::rc::Rc; + +use engine_test_support::{ + internal::{utils, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNTS}, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::{ + execution_result::ExecutionResult, + genesis::{GenesisAccount, POS_REWARDS_PURSE}, + CONV_RATE, +}; +use node::contract_shared::motes::Motes; +use types::{account::AccountHash, runtime_args, Key, RuntimeArgs, URef, U512}; + +const CONTRACT_DO_NOTHING: &str = "do_nothing.wasm"; +const CONTRACT_TRANSFER: &str = "transfer_purse_to_account.wasm"; +const CONTRACT_EE_803_REGRESSION: &str = "ee_803_regression.wasm"; +const COMMAND_BOND: &str = "bond"; +const COMMAND_UNBOND: &str = "unbond"; +const ACCOUNT_ADDR_1: AccountHash = AccountHash::new([1u8; 32]); +const GENESIS_VALIDATOR_STAKE: u64 = 50_000; +const ARG_AMOUNT: &str = "amount"; +const ARG_TARGET: &str = "target"; +const ARG_PURSE: &str = "purse"; +const ARG_ENTRY_POINT_NAME: &str = "method"; + +fn get_pos_purse_by_name(builder: &InMemoryWasmTestBuilder, purse_name: &str) -> Option { + let pos_contract = builder.get_pos_contract(); + + pos_contract + .named_keys() + .get(purse_name) + .and_then(Key::as_uref) + .cloned() +} + +fn get_cost(response: &[Rc]) -> U512 { + let motes = Motes::from_gas( + utils::get_exec_costs(response) + .into_iter() + .fold(Default::default(), |i, acc| i + acc), + CONV_RATE, + ) + .expect("should convert"); + motes.value() +} + +// TODO: should be made more granular when unignored - right now it is meant to demonstrate the +// issue, but once the underlying problem is fixed, the procedure should probably fail at the +// bonding step and we should be asserting that +#[test] +#[ignore] +#[should_panic] +fn should_not_be_able_to_unbond_reward() { + let mut builder = InMemoryWasmTestBuilder::default(); + + let accounts = { + let mut tmp: Vec = DEFAULT_ACCOUNTS.clone(); + let account = GenesisAccount::new( + AccountHash::new([42; 32]), + Motes::new(GENESIS_VALIDATOR_STAKE.into()) * Motes::new(2.into()), + Motes::new(GENESIS_VALIDATOR_STAKE.into()), + ); + tmp.push(account); + tmp + }; + + let run_genesis_request = utils::create_run_genesis_request(accounts); + builder.run_genesis(&run_genesis_request); + + // First request to put some funds in the reward purse + let exec_request_0 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_DO_NOTHING, + RuntimeArgs::default(), + ) + .build(); + + builder.exec(exec_request_0).expect_success().commit(); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER, + runtime_args! { ARG_TARGET => ACCOUNT_ADDR_1, ARG_AMOUNT => U512::from(100) }, + ) + .build(); + + builder.exec(exec_request_1).expect_success().commit(); + + let rewards_purse = get_pos_purse_by_name(&builder, POS_REWARDS_PURSE).unwrap(); + let default_account_purse = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get genesis account") + .main_purse(); + + let rewards_balance_pre = builder.get_purse_balance(rewards_purse); + let default_acc_balance_pre = builder.get_purse_balance(default_account_purse); + let amount_to_steal = U512::from(100_000); + + // try to bond using the funds from the rewards purse (should be illegal) + + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_803_REGRESSION, + runtime_args! { + ARG_ENTRY_POINT_NAME => COMMAND_BOND, + ARG_PURSE => rewards_purse, + ARG_AMOUNT => amount_to_steal + }, + ) + .build(); + + let response_2 = builder + .exec(exec_request_2) + .expect_success() + .commit() + .get_exec_response(2) + .expect("there should be a response") + .to_owned(); + + // try to unbond, thus transferring the funds originally taken from the rewards purse to a + // user's account + + let exec_request_3 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_EE_803_REGRESSION, + runtime_args! { ARG_ENTRY_POINT_NAME => COMMAND_UNBOND }, + ) + .build(); + + let response_3 = builder + .exec(exec_request_3) + .expect_success() + .commit() + .get_exec_response(3) + .expect("there should be a response") + .to_owned(); + + let rewards_balance_post = builder.get_purse_balance(rewards_purse); + let default_acc_balance_post = builder.get_purse_balance(default_account_purse); + + // check that the funds have actually been stolen + + let exec_2_cost = get_cost(&response_2); + let exec_3_cost = get_cost(&response_3); + + assert_eq!( + rewards_balance_post, + rewards_balance_pre + exec_2_cost + exec_3_cost - amount_to_steal + ); + assert_eq!( + default_acc_balance_post, + default_acc_balance_pre - exec_2_cost - exec_3_cost + amount_to_steal + ); +} diff --git a/grpc/tests/src/test/regression/ee_890.rs b/grpc/tests/src/test/regression/ee_890.rs new file mode 100644 index 0000000000..4d11ff1f43 --- /dev/null +++ b/grpc/tests/src/test/regression/ee_890.rs @@ -0,0 +1,78 @@ +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, ARG_AMOUNT, + DEFAULT_PAYMENT, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{runtime_args, RuntimeArgs}; + +const DO_NOTHING_WASM: &str = "do_nothing.wasm"; + +// NOTE: Apparently rustc does not emit "start" when targeting wasm32 +// Ref: https://github.com/rustwasm/team/issues/108 +const CONTRACT_WAT_WITH_START: &str = r#" +(module + (memory (;0;) 1) + (export "memory" (memory 0)) + (type (;0;) (func)) + (func (;0;) (type 0) + nop) + (start 0)) +"#; + +#[ignore] +#[test] +fn should_run_ee_890_gracefully_reject_start_node_in_session() { + let wasm_binary = wabt::wat2wasm(CONTRACT_WAT_WITH_START).expect("should parse"); + + let deploy_1 = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_bytes(wasm_binary, RuntimeArgs::new()) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => *DEFAULT_PAYMENT, }) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([123; 32]) + .build(); + + let exec_request_1 = ExecuteRequestBuilder::new().push_deploy(deploy_1).build(); + + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .commit() + .finish(); + let message = result.builder().exec_error_message(0).expect("should fail"); + assert!( + message.contains("UnsupportedWasmStart"), + "Error message {:?} does not contain expected pattern", + message + ); +} + +#[ignore] +#[test] +fn should_run_ee_890_gracefully_reject_start_node_in_payment() { + let wasm_binary = wabt::wat2wasm(CONTRACT_WAT_WITH_START).expect("should parse"); + + let deploy_1 = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code(DO_NOTHING_WASM, RuntimeArgs::new()) + .with_payment_bytes(wasm_binary, RuntimeArgs::new()) + .with_authorization_keys(&[DEFAULT_ACCOUNT_ADDR]) + .with_deploy_hash([123; 32]) + .build(); + + let exec_request_1 = ExecuteRequestBuilder::new().push_deploy(deploy_1).build(); + + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .commit() + .finish(); + let message = result.builder().exec_error_message(0).expect("should fail"); + assert!( + message.contains("UnsupportedWasmStart"), + "Error message {:?} does not contain expected pattern", + message + ); +} diff --git a/grpc/tests/src/test/regression/mod.rs b/grpc/tests/src/test/regression/mod.rs new file mode 100644 index 0000000000..45eef12d42 --- /dev/null +++ b/grpc/tests/src/test/regression/mod.rs @@ -0,0 +1,20 @@ +mod ee_221; +mod ee_401; +mod ee_441; +mod ee_460; +mod ee_468; +mod ee_470; +mod ee_532; +mod ee_536; +mod ee_539; +mod ee_549; +mod ee_550; +mod ee_572; +mod ee_584; +mod ee_597; +mod ee_598; +mod ee_599; +mod ee_601; +mod ee_771; +mod ee_803; +mod ee_890; diff --git a/grpc/tests/src/test/system_contracts/genesis.rs b/grpc/tests/src/test/system_contracts/genesis.rs new file mode 100644 index 0000000000..e46ca7831b --- /dev/null +++ b/grpc/tests/src/test/system_contracts/genesis.rs @@ -0,0 +1,203 @@ +use engine_test_support::internal::{ + utils, InMemoryWasmTestBuilder, DEFAULT_WASM_COSTS, MINT_INSTALL_CONTRACT, + POS_INSTALL_CONTRACT, STANDARD_PAYMENT_INSTALL_CONTRACT, +}; +use node::contract_core::engine_state::{ + genesis::{ExecConfig, GenesisAccount}, + run_genesis_request::RunGenesisRequest, + SYSTEM_ACCOUNT_ADDR, +}; +use node::contract_shared::{motes::Motes, stored_value::StoredValue}; +use types::{account::AccountHash, ProtocolVersion, U512}; + +#[cfg(feature = "use-system-contracts")] +const BAD_INSTALL: &str = "standard_payment.wasm"; + +const GENESIS_CONFIG_HASH: [u8; 32] = [127; 32]; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const ACCOUNT_2_ADDR: AccountHash = AccountHash::new([2u8; 32]); +const ACCOUNT_1_BONDED_AMOUNT: u64 = 1_000_000; +const ACCOUNT_2_BONDED_AMOUNT: u64 = 2_000_000; +const ACCOUNT_1_BALANCE: u64 = 1_000_000_000; +const ACCOUNT_2_BALANCE: u64 = 2_000_000_000; + +#[ignore] +#[test] +fn should_run_genesis() { + let account_1_balance = Motes::new(ACCOUNT_1_BALANCE.into()); + let account_1 = { + let account_1_account_hash = ACCOUNT_1_ADDR; + let account_1_bonded_amount = Motes::new(ACCOUNT_1_BONDED_AMOUNT.into()); + GenesisAccount::new( + account_1_account_hash, + account_1_balance, + account_1_bonded_amount, + ) + }; + + let account_2_balance = Motes::new(ACCOUNT_2_BALANCE.into()); + let account_2 = { + let account_2_account_hash = ACCOUNT_2_ADDR; + let account_2_bonded_amount = Motes::new(ACCOUNT_2_BONDED_AMOUNT.into()); + GenesisAccount::new( + account_2_account_hash, + account_2_balance, + account_2_bonded_amount, + ) + }; + + let mint_installer_bytes = utils::read_wasm_file_bytes(MINT_INSTALL_CONTRACT); + let pos_installer_bytes = utils::read_wasm_file_bytes(POS_INSTALL_CONTRACT); + let standard_payment_installer_bytes = + utils::read_wasm_file_bytes(STANDARD_PAYMENT_INSTALL_CONTRACT); + let accounts = vec![account_1, account_2]; + let protocol_version = ProtocolVersion::V1_0_0; + let wasm_costs = *DEFAULT_WASM_COSTS; + + let exec_config = ExecConfig::new( + mint_installer_bytes, + pos_installer_bytes, + standard_payment_installer_bytes, + accounts, + wasm_costs, + ); + let run_genesis_request = + RunGenesisRequest::new(GENESIS_CONFIG_HASH.into(), protocol_version, exec_config); + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&run_genesis_request); + + let system_account = builder + .get_account(SYSTEM_ACCOUNT_ADDR) + .expect("system account should exist"); + + let account_1 = builder + .get_account(ACCOUNT_1_ADDR) + .expect("account 1 should exist"); + + let account_2 = builder + .get_account(ACCOUNT_2_ADDR) + .expect("account 2 should exist"); + + let system_account_balance_actual = builder.get_purse_balance(system_account.main_purse()); + let account_1_balance_actual = builder.get_purse_balance(account_1.main_purse()); + let account_2_balance_actual = builder.get_purse_balance(account_2.main_purse()); + + assert_eq!(system_account_balance_actual, U512::zero()); + assert_eq!(account_1_balance_actual, account_1_balance.value()); + assert_eq!(account_2_balance_actual, account_2_balance.value()); + + let mint_contract_hash = builder.get_mint_contract_hash(); + let pos_contract_hash = builder.get_pos_contract_hash(); + + let result = builder.query(None, mint_contract_hash.into(), &[]); + if let Ok(StoredValue::Contract(_)) = result { + // Contract exists at mint contract hash + } else { + panic!("contract not found at mint hash"); + } + + if let Ok(StoredValue::Contract(_)) = builder.query(None, pos_contract_hash.into(), &[]) { + // Contract exists at pos contract hash + } else { + panic!("contract not found at pos hash"); + } +} + +#[cfg(feature = "use-system-contracts")] +#[ignore] +#[should_panic] +#[test] +fn should_fail_if_bad_mint_install_contract_is_provided() { + let run_genesis_request = { + let account_1 = { + let account_1_account_hash = ACCOUNT_1_ADDR; + let account_1_balance = Motes::new(ACCOUNT_1_BALANCE.into()); + let account_1_bonded_amount = Motes::new(ACCOUNT_1_BONDED_AMOUNT.into()); + GenesisAccount::new( + account_1_account_hash, + account_1_balance, + account_1_bonded_amount, + ) + }; + let account_2 = { + let account_2_account_hash = ACCOUNT_2_ADDR; + let account_2_balance = Motes::new(ACCOUNT_2_BALANCE.into()); + let account_2_bonded_amount = Motes::new(ACCOUNT_2_BONDED_AMOUNT.into()); + GenesisAccount::new( + account_2_account_hash, + account_2_balance, + account_2_bonded_amount, + ) + }; + let mint_installer_bytes = utils::read_wasm_file_bytes(BAD_INSTALL); + let pos_installer_bytes = utils::read_wasm_file_bytes(POS_INSTALL_CONTRACT); + let standard_payment_installer_bytes = + utils::read_wasm_file_bytes(STANDARD_PAYMENT_INSTALL_CONTRACT); + let accounts = vec![account_1, account_2]; + let protocol_version = ProtocolVersion::V1_0_0; + let wasm_costs = *DEFAULT_WASM_COSTS; + + let exec_config = ExecConfig::new( + mint_installer_bytes, + pos_installer_bytes, + standard_payment_installer_bytes, + accounts, + wasm_costs, + ); + RunGenesisRequest::new(GENESIS_CONFIG_HASH.into(), protocol_version, exec_config) + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&run_genesis_request); +} + +#[cfg(feature = "use-system-contracts")] +#[ignore] +#[should_panic] +#[test] +fn should_fail_if_bad_pos_install_contract_is_provided() { + let run_genesis_request = { + let account_1 = { + let account_1_account_hash = ACCOUNT_1_ADDR; + let account_1_balance = Motes::new(ACCOUNT_1_BALANCE.into()); + let account_1_bonded_amount = Motes::new(ACCOUNT_1_BONDED_AMOUNT.into()); + GenesisAccount::new( + account_1_account_hash, + account_1_balance, + account_1_bonded_amount, + ) + }; + let account_2 = { + let account_2_account_hash = ACCOUNT_2_ADDR; + let account_2_balance = Motes::new(ACCOUNT_2_BALANCE.into()); + let account_2_bonded_amount = Motes::new(ACCOUNT_2_BONDED_AMOUNT.into()); + GenesisAccount::new( + account_2_account_hash, + account_2_balance, + account_2_bonded_amount, + ) + }; + let mint_installer_bytes = utils::read_wasm_file_bytes(MINT_INSTALL_CONTRACT); + let pos_installer_bytes = utils::read_wasm_file_bytes(BAD_INSTALL); + let standard_payment_installer_bytes = + utils::read_wasm_file_bytes(STANDARD_PAYMENT_INSTALL_CONTRACT); + let accounts = vec![account_1, account_2]; + let protocol_version = ProtocolVersion::V1_0_0; + let wasm_costs = *DEFAULT_WASM_COSTS; + let exec_config = ExecConfig::new( + mint_installer_bytes, + pos_installer_bytes, + standard_payment_installer_bytes, + accounts, + wasm_costs, + ); + RunGenesisRequest::new(GENESIS_CONFIG_HASH.into(), protocol_version, exec_config) + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&run_genesis_request); +} diff --git a/grpc/tests/src/test/system_contracts/mint_install.rs b/grpc/tests/src/test/system_contracts/mint_install.rs new file mode 100644 index 0000000000..ee7bb72bb8 --- /dev/null +++ b/grpc/tests/src/test/system_contracts/mint_install.rs @@ -0,0 +1,75 @@ +use engine_test_support::{ + internal::{ + exec_with_return, WasmTestBuilder, DEFAULT_BLOCK_TIME, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::EngineConfig; +use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use types::{ + contracts::CONTRACT_INITIAL_VERSION, ContractHash, ContractPackageHash, ContractVersionKey, + ProtocolVersion, RuntimeArgs, +}; + +const DEPLOY_HASH_1: [u8; 32] = [1u8; 32]; + +#[ignore] +#[test] +fn should_run_mint_install_contract() { + let mut builder = WasmTestBuilder::default(); + let engine_config = EngineConfig::new() + .with_use_system_contracts(cfg!(feature = "use-system-contracts")) + .with_enable_bonding(cfg!(feature = "enable-bonding")); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let ((contract_package_hash, mint_hash), ret_urefs, effect): ( + (ContractPackageHash, ContractHash), + _, + _, + ) = exec_with_return::exec( + engine_config, + &mut builder, + DEFAULT_ACCOUNT_ADDR, + "mint_install.wasm", + DEFAULT_BLOCK_TIME, + DEPLOY_HASH_1, + "install", + RuntimeArgs::new(), + vec![], + ) + .expect("should run successfully"); + + // does not return extra urefs + assert_eq!(ret_urefs.len(), 0); + assert_ne!(contract_package_hash, mint_hash); + + // should have written a contract under that uref + let contract_package = match effect.transforms.get(&contract_package_hash.into()) { + Some(Transform::Write(StoredValue::ContractPackage(contract_package))) => contract_package, + + _ => panic!("Expected contract package to be written under the key"), + }; + + // Checks if the returned package key contains returned mint key. + assert_eq!(contract_package.versions().len(), 1); + let mint_version = ContractVersionKey::new( + ProtocolVersion::V1_0_0.value().major, + CONTRACT_INITIAL_VERSION, + ); + assert_eq!( + contract_package + .versions() + .get(&mint_version) + .cloned() + .unwrap(), + mint_hash, + ); + + let contract = match effect.transforms.get(&mint_hash.into()) { + Some(Transform::Write(StoredValue::Contract(contract))) => contract, + + _ => panic!("Expected contract to be written under the key"), + }; + assert_eq!(contract.contract_package_hash(), contract_package_hash,); +} diff --git a/grpc/tests/src/test/system_contracts/mod.rs b/grpc/tests/src/test/system_contracts/mod.rs new file mode 100644 index 0000000000..a11b5376e8 --- /dev/null +++ b/grpc/tests/src/test/system_contracts/mod.rs @@ -0,0 +1,7 @@ +mod genesis; +mod mint_install; +mod pos_install; +mod proof_of_stake; +mod standard_payment; +mod standard_payment_install; +mod upgrade; diff --git a/grpc/tests/src/test/system_contracts/pos_install.rs b/grpc/tests/src/test/system_contracts/pos_install.rs new file mode 100644 index 0000000000..46a13624d6 --- /dev/null +++ b/grpc/tests/src/test/system_contracts/pos_install.rs @@ -0,0 +1,137 @@ +#![allow(dead_code)] +#![allow(unused_imports)] + +use std::collections::BTreeMap; + +use engine_test_support::{ + internal::{ + exec_with_return, ExecuteRequestBuilder, WasmTestBuilder, DEFAULT_BLOCK_TIME, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::EngineConfig; +use types::{ + account::AccountHash, contracts::NamedKeys, runtime_args, ContractHash, ContractPackageHash, + Key, RuntimeArgs, URef, U512, +}; + +const CONTRACT_TRANSFER_TO_ACCOUNT: &str = "transfer_to_account_u512.wasm"; +const TRANSFER_AMOUNT: u64 = 250_000_000 + 1000; +const SYSTEM_ADDR: AccountHash = AccountHash::new([0u8; 32]); +const DEPLOY_HASH_2: [u8; 32] = [2u8; 32]; +const N_VALIDATORS: u8 = 5; + +// one named_key for each validator and three for the purses +const EXPECTED_KNOWN_KEYS_LEN: usize = (N_VALIDATORS as usize) + 3; + +const POS_BONDING_PURSE: &str = "pos_bonding_purse"; +const POS_PAYMENT_PURSE: &str = "pos_payment_purse"; +const POS_REWARDS_PURSE: &str = "pos_rewards_purse"; + +const ARG_MINT_PACKAGE_HASH: &str = "mint_contract_package_hash"; +const ARG_GENESIS_VALIDATORS: &str = "genesis_validators"; + +#[ignore] +#[test] +fn should_run_pos_install_contract() { + let mut builder = WasmTestBuilder::default(); + let engine_config = EngineConfig::new() + .with_use_system_contracts(cfg!(feature = "use-system-contracts")) + .with_enable_bonding(cfg!(feature = "enable-bonding")); + + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_TO_ACCOUNT, + runtime_args! { "target" =>SYSTEM_ADDR, "amount" => U512::from(TRANSFER_AMOUNT) }, + ) + .build(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + builder.exec(exec_request).commit().expect_success(); + + let mint_hash = builder.get_mint_contract_hash(); + + let mint_package_stored_value = builder + .query(None, mint_hash.into(), &[]) + .expect("should query mint hash"); + let mint_package = mint_package_stored_value + .as_contract() + .expect("should be contract"); + + let mint_package_hash = mint_package.contract_package_hash(); + + let genesis_validators: BTreeMap = (1u8..=N_VALIDATORS) + .map(|i| (AccountHash::new([i; 32]), U512::from(i))) + .collect(); + + let total_bond = genesis_validators.values().fold(U512::zero(), |x, y| x + y); + + let res = exec_with_return::exec( + engine_config, + &mut builder, + SYSTEM_ADDR, + "pos_install.wasm", + DEFAULT_BLOCK_TIME, + DEPLOY_HASH_2, + "install", + runtime_args! { + ARG_MINT_PACKAGE_HASH => mint_package_hash, + ARG_GENESIS_VALIDATORS => genesis_validators, + }, + vec![], + ); + let ((_pos_package_hash, pos_hash), _ret_urefs, effect): ( + (ContractPackageHash, ContractHash), + _, + _, + ) = res.expect("should run successfully"); + + let prestate = builder.get_post_state_hash(); + builder.commit_effects(prestate, effect.transforms); + + // should return a hash + //assert_eq!(ret_value, ret_urefs[0]); + + // should have written a contract under that uref + let contract = builder + .get_contract(pos_hash) + .expect("should have a contract"); + let named_keys = contract.named_keys(); + + assert_eq!(named_keys.len(), EXPECTED_KNOWN_KEYS_LEN); + + // bonding purse has correct balance + let bonding_purse = get_purse(named_keys, POS_BONDING_PURSE).expect( + "should find bonding purse in + named_keys", + ); + + let bonding_purse_balance = builder.get_purse_balance(bonding_purse); + assert_eq!(bonding_purse_balance, total_bond); + + // payment purse has correct balance + let payment_purse = get_purse(named_keys, POS_PAYMENT_PURSE).expect( + "should find payment purse in + named_keys", + ); + + let payment_purse_balance = builder.get_purse_balance(payment_purse); + assert_eq!(payment_purse_balance, U512::zero()); + + // rewards purse has correct balance + let rewards_purse = get_purse(named_keys, POS_REWARDS_PURSE).expect( + "should find rewards purse in + named_keys", + ); + + let rewards_purse_balance = builder.get_purse_balance(rewards_purse); + assert_eq!(rewards_purse_balance, U512::zero()); +} + +fn get_purse(named_keys: &NamedKeys, name: &str) -> Option { + named_keys + .get(name) + .expect("should have named key") + .into_uref() +} diff --git a/grpc/tests/src/test/system_contracts/proof_of_stake/bonding.rs b/grpc/tests/src/test/system_contracts/proof_of_stake/bonding.rs new file mode 100644 index 0000000000..96ab6bb22d --- /dev/null +++ b/grpc/tests/src/test/system_contracts/proof_of_stake/bonding.rs @@ -0,0 +1,537 @@ +use engine_test_support::{ + internal::{ + utils, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNTS, DEFAULT_PAYMENT, + }, + DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, +}; +use node::contract_core::engine_state::{ + genesis::{GenesisAccount, POS_BONDING_PURSE}, + CONV_RATE, +}; +use node::contract_shared::motes::Motes; +use types::{account::AccountHash, runtime_args, ApiError, Key, RuntimeArgs, URef, U512}; + +const CONTRACT_POS_BONDING: &str = "pos_bonding.wasm"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const ACCOUNT_1_SEED_AMOUNT: u64 = 100_000_000 * 2; +const ACCOUNT_1_STAKE: u64 = 42_000; +const ACCOUNT_1_UNBOND_1: u64 = 22_000; +const ACCOUNT_1_UNBOND_2: u64 = 20_000; + +const GENESIS_VALIDATOR_STAKE: u64 = 50_000; +const GENESIS_ACCOUNT_STAKE: u64 = 100_000; +const GENESIS_ACCOUNT_UNBOND_1: u64 = 45_000; +const GENESIS_ACCOUNT_UNBOND_2: u64 = 55_000; + +const TEST_BOND: &str = "bond"; +const TEST_BOND_FROM_MAIN_PURSE: &str = "bond-from-main-purse"; +const TEST_SEED_NEW_ACCOUNT: &str = "seed_new_account"; +const TEST_UNBOND: &str = "unbond"; + +const ARG_AMOUNT: &str = "amount"; +const ARG_ENTRY_POINT: &str = "entry_point"; +const ARG_ACCOUNT_PK: &str = "account_hash"; + +fn get_pos_purse_by_name(builder: &InMemoryWasmTestBuilder, purse_name: &str) -> Option { + let pos_contract = builder.get_pos_contract(); + + pos_contract + .named_keys() + .get(purse_name) + .and_then(Key::as_uref) + .cloned() +} + +fn get_pos_bonding_purse_balance(builder: &InMemoryWasmTestBuilder) -> U512 { + let purse = + get_pos_purse_by_name(builder, POS_BONDING_PURSE).expect("should find PoS payment purse"); + builder.get_purse_balance(purse) +} + +#[ignore] +#[test] +fn should_run_successful_bond_and_unbond() { + let accounts = { + let mut tmp: Vec = DEFAULT_ACCOUNTS.clone(); + let account = GenesisAccount::new( + AccountHash::new([42; 32]), + Motes::new(GENESIS_VALIDATOR_STAKE.into()) * Motes::new(2.into()), + Motes::new(GENESIS_VALIDATOR_STAKE.into()), + ); + tmp.push(account); + tmp + }; + + let run_genesis_request = utils::create_run_genesis_request(accounts); + + let mut builder = InMemoryWasmTestBuilder::default(); + let result = builder.run_genesis(&run_genesis_request).finish(); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get account 1"); + + let pos = builder.get_pos_contract_hash(); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_POS_BONDING, + runtime_args! { + ARG_ENTRY_POINT => String::from(TEST_BOND), + ARG_AMOUNT => U512::from(GENESIS_ACCOUNT_STAKE) + }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::from_result(result); + + let result = builder.exec(exec_request_1); + if !cfg!(feature = "enable-bonding") && result.is_error() { + return; + } + + let result = builder.expect_success().commit().finish(); + + let exec_response = builder + .get_exec_response(0) + .expect("should have exec response"); + let mut genesis_gas_cost = utils::get_exec_costs(exec_response)[0]; + + let contract = builder.get_contract(pos).expect("should have contract"); + + let lookup_key = format!( + "v_{}_{}", + base16::encode_lower(&DEFAULT_ACCOUNT_ADDR.as_bytes()), + GENESIS_ACCOUNT_STAKE + ); + assert!(contract.named_keys().contains_key(&lookup_key)); + + // Gensis validator [42; 32] bonded 50k, and genesis account bonded 100k inside + // the test contract + assert_eq!( + get_pos_bonding_purse_balance(&builder), + U512::from(GENESIS_VALIDATOR_STAKE + GENESIS_ACCOUNT_STAKE) + ); + + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_POS_BONDING, + runtime_args! { + ARG_ENTRY_POINT => TEST_SEED_NEW_ACCOUNT, + ARG_ACCOUNT_PK => ACCOUNT_1_ADDR, + ARG_AMOUNT => U512::from(ACCOUNT_1_SEED_AMOUNT), + }, + ) + .build(); + + let exec_request_3 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_POS_BONDING, + runtime_args! { + ARG_ENTRY_POINT => String::from(TEST_BOND_FROM_MAIN_PURSE), + ARG_AMOUNT => U512::from(ACCOUNT_1_STAKE), + }, + ) + .build(); + + // Create new account (from genesis funds) and bond with it + let mut builder = InMemoryWasmTestBuilder::from_result(result); + let result = builder + .exec(exec_request_2) + .expect_success() + .commit() + .exec(exec_request_3) + .expect_success() + .commit() + .finish(); + + let exec_response = builder + .get_exec_response(0) + .expect("should have exec response"); + genesis_gas_cost = genesis_gas_cost + utils::get_exec_costs(exec_response)[0]; + + let account_1 = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should get account 1"); + + let pos = builder.get_pos_contract_hash(); + + // Verify that genesis account is in validator queue + let contract = builder.get_contract(pos).expect("should have contract"); + + let lookup_key = format!( + "v_{}_{}", + base16::encode_lower(ACCOUNT_1_ADDR.as_bytes()), + ACCOUNT_1_STAKE + ); + assert!(contract.named_keys().contains_key(&lookup_key)); + + // Gensis validator [42; 32] bonded 50k, and genesis account bonded 100k inside + // the test contract + let pos_bonding_purse_balance = get_pos_bonding_purse_balance(&builder); + assert_eq!( + pos_bonding_purse_balance, + U512::from(GENESIS_VALIDATOR_STAKE + GENESIS_ACCOUNT_STAKE + ACCOUNT_1_STAKE) + ); + + // + // Stage 2a - Account 1 unbonds by decreasing less than 50% (and is still in the + // queue) + // + let exec_request_4 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_POS_BONDING, + runtime_args! { + ARG_ENTRY_POINT => String::from(TEST_UNBOND), + ARG_AMOUNT => Some(U512::from(ACCOUNT_1_UNBOND_1)), + }, + ) + .build(); + let account_1_bal_before = builder.get_purse_balance(account_1.main_purse()); + let mut builder = InMemoryWasmTestBuilder::from_result(result); + let result = builder + .exec(exec_request_4) + .expect_success() + .commit() + .finish(); + + let account_1_bal_after = builder.get_purse_balance(account_1.main_purse()); + let exec_response = builder + .get_exec_response(0) + .expect("should have exec response"); + let gas_cost_b = Motes::from_gas(utils::get_exec_costs(exec_response)[0], CONV_RATE) + .expect("should convert"); + + assert_eq!( + account_1_bal_after, + account_1_bal_before - gas_cost_b.value() + ACCOUNT_1_UNBOND_1, + ); + + // POS bonding purse is decreased + assert_eq!( + get_pos_bonding_purse_balance(&builder), + U512::from(GENESIS_VALIDATOR_STAKE + GENESIS_ACCOUNT_STAKE + ACCOUNT_1_UNBOND_2) + ); + + let pos_contract = builder.get_pos_contract(); + + let lookup_key = format!( + "v_{}_{}", + base16::encode_lower(ACCOUNT_1_ADDR.as_bytes()), + ACCOUNT_1_STAKE + ); + assert!(!pos_contract.named_keys().contains_key(&lookup_key)); + + let lookup_key = format!( + "v_{}_{}", + base16::encode_lower(ACCOUNT_1_ADDR.as_bytes()), + ACCOUNT_1_UNBOND_2 + ); + // Account 1 is still tracked anymore in the bonding queue with different uref + // name + assert!(pos_contract.named_keys().contains_key(&lookup_key)); + + // + // Stage 2b - Genesis unbonds by decreasing less than 50% (and is still in the + // queue) + // + // Genesis account unbonds less than 50% of his stake + let exec_request_5 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_POS_BONDING, + runtime_args! { + ARG_ENTRY_POINT => String::from(TEST_UNBOND), + ARG_AMOUNT => Some(U512::from(GENESIS_ACCOUNT_UNBOND_1)), + }, + ) + .build(); + let mut builder = InMemoryWasmTestBuilder::from_result(result); + let result = builder + .exec(exec_request_5) + .expect_success() + .commit() + .finish(); + + let exec_response = builder + .get_exec_response(0) + .expect("should have exec response"); + genesis_gas_cost = genesis_gas_cost + utils::get_exec_costs(exec_response)[0]; + + assert_eq!( + builder.get_purse_balance(default_account.main_purse()), + U512::from( + DEFAULT_ACCOUNT_INITIAL_BALANCE + - Motes::from_gas(genesis_gas_cost, CONV_RATE) + .expect("should convert") + .value() + .as_u64() + - ACCOUNT_1_SEED_AMOUNT + - GENESIS_ACCOUNT_UNBOND_2 + ), + ); + + // POS bonding purse is further decreased + assert_eq!( + get_pos_bonding_purse_balance(&builder), + U512::from(GENESIS_VALIDATOR_STAKE + GENESIS_ACCOUNT_UNBOND_2 + ACCOUNT_1_UNBOND_2) + ); + + // + // Stage 3a - Fully unbond account1 with Some(TOTAL_AMOUNT) + // + let account_1_bal_before = builder.get_purse_balance(account_1.main_purse()); + + let exec_request_6 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_POS_BONDING, + runtime_args! { + ARG_ENTRY_POINT => String::from(TEST_UNBOND), + ARG_AMOUNT => Some(U512::from(ACCOUNT_1_UNBOND_2)), + }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::from_result(result); + let result = builder + .exec(exec_request_6) + .expect_success() + .commit() + .finish(); + + let account_1_bal_after = builder.get_purse_balance(account_1.main_purse()); + let exec_response = builder + .get_exec_response(0) + .expect("should have exec response"); + let gas_cost_b = Motes::from_gas(utils::get_exec_costs(exec_response)[0], CONV_RATE) + .expect("should convert"); + + assert_eq!( + account_1_bal_after, + account_1_bal_before - gas_cost_b.value() + ACCOUNT_1_UNBOND_2, + ); + + // POS bonding purse contains now genesis validator (50k) + genesis account + // (55k) + assert_eq!( + get_pos_bonding_purse_balance(&builder), + U512::from(GENESIS_VALIDATOR_STAKE + GENESIS_ACCOUNT_UNBOND_2) + ); + + let pos_contract = builder.get_pos_contract(); + + let lookup_key = format!( + "v_{}_{}", + base16::encode_lower(ACCOUNT_1_ADDR.as_bytes()), + ACCOUNT_1_UNBOND_2 + ); + // Account 1 isn't tracked anymore in the bonding queue + assert!(!pos_contract.named_keys().contains_key(&lookup_key)); + + // + // Stage 3b - Fully unbond account1 with Some(TOTAL_AMOUNT) + // + + // Genesis account unbonds less than 50% of his stake + let exec_request_7 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_POS_BONDING, + runtime_args! { + ARG_ENTRY_POINT => String::from(TEST_UNBOND), + ARG_AMOUNT => None as Option + }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::from_result(result); + let result = builder + .exec(exec_request_7) + .expect_success() + .commit() + .finish(); + + let exec_response = builder + .get_exec_response(0) + .expect("should have exec response"); + genesis_gas_cost = genesis_gas_cost + utils::get_exec_costs(exec_response)[0]; + + // Back to original after funding account1's pursee + assert_eq!( + result + .builder() + .get_purse_balance(default_account.main_purse()), + U512::from( + DEFAULT_ACCOUNT_INITIAL_BALANCE + - Motes::from_gas(genesis_gas_cost, CONV_RATE) + .expect("should convert") + .value() + .as_u64() + - ACCOUNT_1_SEED_AMOUNT + ) + ); + + // Final balance after two full unbonds is the initial bond valuee + assert_eq!( + get_pos_bonding_purse_balance(&builder), + U512::from(GENESIS_VALIDATOR_STAKE) + ); + + let pos_contract = builder.get_pos_contract(); + let lookup_key = format!( + "v_{}_{}", + base16::encode_lower(&DEFAULT_ACCOUNT_ADDR.as_bytes()), + GENESIS_ACCOUNT_UNBOND_2 + ); + // Genesis is still tracked anymore in the bonding queue with different uref + // name + assert!(!pos_contract.named_keys().contains_key(&lookup_key)); + + // + // Final checks on validator queue + // + + // Account 1 is still tracked anymore in the bonding queue with any amount + // suffix + assert_eq!( + pos_contract + .named_keys() + .iter() + .filter(|(key, _)| key.starts_with(&format!( + "v_{}", + base16::encode_lower(&DEFAULT_ACCOUNT_ADDR.as_bytes()) + ))) + .count(), + 0 + ); + assert_eq!( + pos_contract + .named_keys() + .iter() + .filter(|(key, _)| key.starts_with(&format!( + "v_{}", + base16::encode_lower(ACCOUNT_1_ADDR.as_bytes()) + ))) + .count(), + 0 + ); + // only genesis validator is still in the queue + assert_eq!( + pos_contract + .named_keys() + .iter() + .filter(|(key, _)| key.starts_with("v_")) + .count(), + 1 + ); +} + +#[ignore] +#[test] +fn should_fail_bonding_with_insufficient_funds() { + let accounts = { + let mut tmp: Vec = DEFAULT_ACCOUNTS.clone(); + let account = GenesisAccount::new( + AccountHash::new([42; 32]), + Motes::new(GENESIS_VALIDATOR_STAKE.into()) * Motes::new(2.into()), + Motes::new(GENESIS_VALIDATOR_STAKE.into()), + ); + tmp.push(account); + tmp + }; + + let run_genesis_request = utils::create_run_genesis_request(accounts); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_POS_BONDING, + runtime_args! { + ARG_ENTRY_POINT => TEST_SEED_NEW_ACCOUNT, + ARG_ACCOUNT_PK => ACCOUNT_1_ADDR, + ARG_AMOUNT => *DEFAULT_PAYMENT + GENESIS_ACCOUNT_STAKE, + }, + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_POS_BONDING, + runtime_args! { + ARG_ENTRY_POINT => TEST_BOND_FROM_MAIN_PURSE, + ARG_AMOUNT => *DEFAULT_PAYMENT + GENESIS_ACCOUNT_STAKE, + }, + ) + .build(); + + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&run_genesis_request) + .exec(exec_request_1) + .commit() + .exec(exec_request_2) + .commit() + .finish(); + + let response = result + .builder() + .get_exec_response(1) + .expect("should have a response") + .to_owned(); + + let error_message = utils::get_error_message(response); + + if !cfg!(feature = "enable-bonding") { + assert!( + error_message.contains(&format!("{:?}", ApiError::Unhandled)), + "error is {:?}", + error_message + ); + } else { + // pos::Error::BondTransferFailed => 8 + assert!(error_message.contains(&format!("{:?}", ApiError::ProofOfStake(8)))); + } +} + +#[ignore] +#[test] +fn should_fail_unbonding_validator_without_bonding_first() { + let accounts = { + let mut tmp: Vec = DEFAULT_ACCOUNTS.clone(); + let account = GenesisAccount::new( + AccountHash::new([42; 32]), + Motes::new(GENESIS_VALIDATOR_STAKE.into()) * Motes::new(2.into()), + Motes::new(GENESIS_VALIDATOR_STAKE.into()), + ); + tmp.push(account); + tmp + }; + + let run_genesis_request = utils::create_run_genesis_request(accounts); + + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_POS_BONDING, + runtime_args! { + ARG_ENTRY_POINT => TEST_UNBOND, + ARG_AMOUNT => Some(U512::from(42)), + }, + ) + .build(); + + let result = InMemoryWasmTestBuilder::default() + .run_genesis(&run_genesis_request) + .exec(exec_request) + .commit() + .finish(); + + let response = result + .builder() + .get_exec_response(0) + .expect("should have a response") + .to_owned(); + + let error_message = utils::get_error_message(response); + + if !cfg!(feature = "enable-bonding") { + assert!(error_message.contains(&format!("{:?}", ApiError::Unhandled))); + } else { + // pos::Error::NotBonded => 0 + assert!(error_message.contains(&format!("{:?}", ApiError::ProofOfStake(0)))); + } +} diff --git a/grpc/tests/src/test/system_contracts/proof_of_stake/commit_validators.rs b/grpc/tests/src/test/system_contracts/proof_of_stake/commit_validators.rs new file mode 100644 index 0000000000..18c10d0e33 --- /dev/null +++ b/grpc/tests/src/test/system_contracts/proof_of_stake/commit_validators.rs @@ -0,0 +1,75 @@ +use num_traits::Zero; +use std::collections::HashMap; + +use engine_test_support::{ + internal::{utils, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNTS}, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::genesis::GenesisAccount; +use node::contract_shared::motes::Motes; +use types::{account::AccountHash, RuntimeArgs, U512}; + +const CONTRACT_LOCAL_STATE: &str = "do_nothing.wasm"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const ACCOUNT_1_BALANCE: u64 = 2000; +const ACCOUNT_1_BOND: u64 = 1000; + +const ACCOUNT_2_ADDR: AccountHash = AccountHash::new([2u8; 32]); +const ACCOUNT_2_BALANCE: u64 = 2000; +const ACCOUNT_2_BOND: u64 = 200; + +#[ignore] +#[test] +fn should_return_bonded_validators() { + let accounts = { + let mut tmp: Vec = DEFAULT_ACCOUNTS.clone(); + let account_1 = GenesisAccount::new( + ACCOUNT_1_ADDR, + Motes::new(ACCOUNT_1_BALANCE.into()), + Motes::new(ACCOUNT_1_BOND.into()), + ); + let account_2 = GenesisAccount::new( + ACCOUNT_2_ADDR, + Motes::new(ACCOUNT_2_BALANCE.into()), + Motes::new(ACCOUNT_2_BOND.into()), + ); + tmp.push(account_1); + tmp.push(account_2); + tmp + }; + + let run_genesis_request = utils::create_run_genesis_request(accounts.clone()); + + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_LOCAL_STATE, + RuntimeArgs::default(), + ) + .build(); + + let actual = InMemoryWasmTestBuilder::default() + .run_genesis(&run_genesis_request) + .exec(exec_request) + .commit() + .get_bonded_validators()[0] + .clone(); + + let expected: HashMap = { + let zero = Motes::zero(); + accounts + .iter() + .filter_map(move |genesis_account| { + if genesis_account.bonded_amount() > zero { + Some(( + genesis_account.account_hash(), + genesis_account.bonded_amount().value(), + )) + } else { + None + } + }) + .collect() + }; + + assert_eq!(actual, expected); +} diff --git a/grpc/tests/src/test/system_contracts/proof_of_stake/finalize_payment.rs b/grpc/tests/src/test/system_contracts/proof_of_stake/finalize_payment.rs new file mode 100644 index 0000000000..94e75fea7e --- /dev/null +++ b/grpc/tests/src/test/system_contracts/proof_of_stake/finalize_payment.rs @@ -0,0 +1,222 @@ +use std::convert::TryInto; + +use engine_test_support::{ + internal::{ + utils, DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::{ + genesis::{POS_PAYMENT_PURSE, POS_REWARDS_PURSE}, + CONV_RATE, +}; +use node::contract_shared::{account::Account, motes::Motes}; +use types::{account::AccountHash, runtime_args, Key, RuntimeArgs, URef, U512}; + +const CONTRACT_FINALIZE_PAYMENT: &str = "pos_finalize_payment.wasm"; +const CONTRACT_TRANSFER_PURSE_TO_ACCOUNT: &str = "transfer_purse_to_account.wasm"; +const FINALIZE_PAYMENT: &str = "pos_finalize_payment.wasm"; +const LOCAL_REFUND_PURSE: &str = "local_refund_purse"; +const POS_REFUND_PURSE_NAME: &str = "pos_refund_purse"; + +const SYSTEM_ADDR: AccountHash = AccountHash::new([0u8; 32]); +const ACCOUNT_ADDR: AccountHash = AccountHash::new([1u8; 32]); +pub const ARG_AMOUNT: &str = "amount"; +pub const ARG_AMOUNT_SPENT: &str = "amount_spent"; +pub const ARG_REFUND_FLAG: &str = "refund"; +pub const ARG_ACCOUNT_KEY: &str = "account"; +pub const ARG_TARGET: &str = "target"; + +fn initialize() -> InMemoryWasmTestBuilder { + let mut builder = InMemoryWasmTestBuilder::default(); + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { ARG_TARGET => SYSTEM_ADDR, ARG_AMOUNT => *DEFAULT_PAYMENT }, + ) + .build(); + + let exec_request_2 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_ADDR, ARG_AMOUNT => *DEFAULT_PAYMENT }, + ) + .build(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder.exec(exec_request_1).expect_success().commit(); + + builder.exec(exec_request_2).expect_success().commit(); + + builder +} + +#[ignore] +#[test] +fn finalize_payment_should_not_be_run_by_non_system_accounts() { + let mut builder = initialize(); + let payment_amount = U512::from(300); + let spent_amount = U512::from(75); + let refund_purse: Option = None; + let args = runtime_args! { + ARG_AMOUNT => payment_amount, + ARG_REFUND_FLAG => refund_purse, + ARG_AMOUNT_SPENT => Some(spent_amount), + ARG_ACCOUNT_KEY => Some(ACCOUNT_ADDR), + }; + + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_FINALIZE_PAYMENT, + args.clone(), + ) + .build(); + let exec_request_2 = + ExecuteRequestBuilder::standard(ACCOUNT_ADDR, CONTRACT_FINALIZE_PAYMENT, args).build(); + + assert!(builder.exec(exec_request_1).is_error()); + + assert!(builder.exec(exec_request_2).is_error()); +} + +#[ignore] +#[test] +fn finalize_payment_should_refund_to_specified_purse() { + let mut builder = InMemoryWasmTestBuilder::default(); + let payment_amount = *DEFAULT_PAYMENT; + let refund_purse_flag: u8 = 1; + // Don't need to run finalize_payment manually, it happens during + // the deploy because payment code is enabled. + let args = runtime_args! { + ARG_AMOUNT => payment_amount, + ARG_REFUND_FLAG => refund_purse_flag, + ARG_AMOUNT_SPENT => Option::::None, + ARG_ACCOUNT_KEY => Option::::None, + }; + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let payment_pre_balance = get_pos_payment_purse_balance(&builder); + let rewards_pre_balance = get_pos_rewards_purse_balance(&builder); + let refund_pre_balance = + get_named_account_balance(&builder, DEFAULT_ACCOUNT_ADDR, LOCAL_REFUND_PURSE) + .unwrap_or_else(U512::zero); + + assert!( + get_pos_refund_purse(&builder).is_none(), + "refund_purse should start unset" + ); + assert!( + payment_pre_balance.is_zero(), + "payment purse should start with zero balance" + ); + + let exec_request = { + let genesis_account_hash = DEFAULT_ACCOUNT_ADDR; + + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_session_code("do_nothing.wasm", RuntimeArgs::default()) + .with_payment_code(FINALIZE_PAYMENT, args) + .with_authorization_keys(&[genesis_account_hash]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + builder.exec(exec_request).expect_success().commit(); + + let spent_amount: U512 = { + let response = builder + .get_exec_response(0) + .expect("there should be a response"); + + let success_result = utils::get_success_result(response); + Motes::from_gas(success_result.cost(), CONV_RATE) + .expect("should have motes") + .value() + }; + + let payment_post_balance = get_pos_payment_purse_balance(&builder); + let rewards_post_balance = get_pos_rewards_purse_balance(&builder); + let refund_post_balance = + get_named_account_balance(&builder, DEFAULT_ACCOUNT_ADDR, LOCAL_REFUND_PURSE) + .expect("should have refund balance"); + let expected_amount = rewards_pre_balance + spent_amount; + assert_eq!( + expected_amount, rewards_post_balance, + "validators should get paid; expected: {}, actual: {}", + expected_amount, rewards_post_balance + ); + + // user gets refund + assert_eq!( + refund_pre_balance + payment_amount - spent_amount, + refund_post_balance, + "user should get refund" + ); + + assert!( + get_pos_refund_purse(&builder).is_none(), + "refund_purse always ends unset" + ); + assert!( + payment_post_balance.is_zero(), + "payment purse should ends with zero balance" + ); +} + +// ------------- utility functions -------------------- // + +fn get_pos_payment_purse_balance(builder: &InMemoryWasmTestBuilder) -> U512 { + let purse = + get_pos_purse_by_name(builder, POS_PAYMENT_PURSE).expect("should find PoS payment purse"); + builder.get_purse_balance(purse) +} + +fn get_pos_rewards_purse_balance(builder: &InMemoryWasmTestBuilder) -> U512 { + let purse = + get_pos_purse_by_name(builder, POS_REWARDS_PURSE).expect("should find PoS rewards purse"); + builder.get_purse_balance(purse) +} + +fn get_pos_refund_purse(builder: &InMemoryWasmTestBuilder) -> Option { + let pos_contract = builder.get_pos_contract(); + pos_contract + .named_keys() + .get(POS_REFUND_PURSE_NAME) + .cloned() +} + +fn get_pos_purse_by_name(builder: &InMemoryWasmTestBuilder, purse_name: &str) -> Option { + let pos_contract = builder.get_pos_contract(); + pos_contract + .named_keys() + .get(purse_name) + .and_then(Key::as_uref) + .cloned() +} + +fn get_named_account_balance( + builder: &InMemoryWasmTestBuilder, + account_address: AccountHash, + name: &str, +) -> Option { + let account_key = Key::Account(account_address); + + let account: Account = builder + .query(None, account_key, &[]) + .and_then(|v| v.try_into().map_err(|error| format!("{:?}", error))) + .expect("should find balance uref"); + + let purse = account + .named_keys() + .get(name) + .and_then(Key::as_uref) + .cloned(); + + purse.map(|uref| builder.get_purse_balance(uref)) +} diff --git a/grpc/tests/src/test/system_contracts/proof_of_stake/get_payment_purse.rs b/grpc/tests/src/test/system_contracts/proof_of_stake/get_payment_purse.rs new file mode 100644 index 0000000000..13e7df57fb --- /dev/null +++ b/grpc/tests/src/test/system_contracts/proof_of_stake/get_payment_purse.rs @@ -0,0 +1,59 @@ +use engine_test_support::{ + internal::{ + ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; + +const CONTRACT_POS_GET_PAYMENT_PURSE: &str = "pos_get_payment_purse.wasm"; +const CONTRACT_TRANSFER_PURSE_TO_ACCOUNT: &str = "transfer_purse_to_account.wasm"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const ACCOUNT_1_INITIAL_BALANCE: u64 = 100_000_000 + 100; +const ARG_AMOUNT: &str = "amount"; + +#[ignore] +#[test] +fn should_run_get_payment_purse_contract_default_account() { + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_POS_GET_PAYMENT_PURSE, + runtime_args! { + ARG_AMOUNT => *DEFAULT_PAYMENT, + }, + ) + .build(); + InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .expect_success() + .commit(); +} + +#[ignore] +#[test] +fn should_run_get_payment_purse_contract_account_1() { + let exec_request_1 = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { "target" =>ACCOUNT_1_ADDR, "amount" => U512::from(ACCOUNT_1_INITIAL_BALANCE) }, + ) + .build(); + let exec_request_2 = ExecuteRequestBuilder::standard( + ACCOUNT_1_ADDR, + CONTRACT_POS_GET_PAYMENT_PURSE, + runtime_args! { + ARG_AMOUNT => *DEFAULT_PAYMENT, + }, + ) + .build(); + InMemoryWasmTestBuilder::default() + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request_1) + .expect_success() + .commit() + .exec(exec_request_2) + .expect_success() + .commit(); +} diff --git a/grpc/tests/src/test/system_contracts/proof_of_stake/mod.rs b/grpc/tests/src/test/system_contracts/proof_of_stake/mod.rs new file mode 100644 index 0000000000..95b2105d63 --- /dev/null +++ b/grpc/tests/src/test/system_contracts/proof_of_stake/mod.rs @@ -0,0 +1,5 @@ +mod bonding; +mod commit_validators; +mod finalize_payment; +mod get_payment_purse; +mod refund_purse; diff --git a/grpc/tests/src/test/system_contracts/proof_of_stake/refund_purse.rs b/grpc/tests/src/test/system_contracts/proof_of_stake/refund_purse.rs new file mode 100644 index 0000000000..29373b2241 --- /dev/null +++ b/grpc/tests/src/test/system_contracts/proof_of_stake/refund_purse.rs @@ -0,0 +1,70 @@ +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; + +const CONTRACT_TRANSFER_PURSE_TO_ACCOUNT: &str = "transfer_purse_to_account.wasm"; +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const ARG_PAYMENT_AMOUNT: &str = "payment_amount"; + +#[ignore] +#[test] +fn should_run_pos_refund_purse_contract_default_account() { + let mut builder = initialize(); + refund_tests(&mut builder, DEFAULT_ACCOUNT_ADDR); +} + +#[ignore] +#[test] +fn should_run_pos_refund_purse_contract_account_1() { + let mut builder = initialize(); + transfer(&mut builder, ACCOUNT_1_ADDR, *DEFAULT_PAYMENT * 2); + refund_tests(&mut builder, ACCOUNT_1_ADDR); +} + +fn initialize() -> InMemoryWasmTestBuilder { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + builder +} + +fn transfer(builder: &mut InMemoryWasmTestBuilder, account_hash: AccountHash, amount: U512) { + let exec_request = { + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { + "target" => account_hash, + "amount" => amount, + }, + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); +} + +fn refund_tests(builder: &mut InMemoryWasmTestBuilder, account_hash: AccountHash) { + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(account_hash) + .with_deploy_hash([2; 32]) + .with_session_code("do_nothing.wasm", RuntimeArgs::default()) + .with_payment_code( + "pos_refund_purse.wasm", + runtime_args! { ARG_PAYMENT_AMOUNT => *DEFAULT_PAYMENT }, + ) + .with_authorization_keys(&[account_hash]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + builder.exec(exec_request).expect_success().commit(); +} diff --git a/grpc/tests/src/test/system_contracts/standard_payment.rs b/grpc/tests/src/test/system_contracts/standard_payment.rs new file mode 100644 index 0000000000..3bb93c8d12 --- /dev/null +++ b/grpc/tests/src/test/system_contracts/standard_payment.rs @@ -0,0 +1,712 @@ +use assert_matches::assert_matches; + +use engine_test_support::{ + internal::{ + utils, DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, + DEFAULT_ACCOUNT_KEY, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, +}; +use node::contract_core::{ + engine_state::{genesis::POS_REWARDS_PURSE, Error, CONV_RATE, MAX_PAYMENT}, + execution, +}; +use node::contract_shared::{motes::Motes, transform::Transform}; +use types::{account::AccountHash, runtime_args, ApiError, Key, RuntimeArgs, URef, U512}; + +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([42u8; 32]); +const DO_NOTHING_WASM: &str = "do_nothing.wasm"; +const TRANSFER_PURSE_TO_ACCOUNT_WASM: &str = "transfer_purse_to_account.wasm"; +const REVERT_WASM: &str = "revert.wasm"; +const ENDLESS_LOOP_WASM: &str = "endless_loop.wasm"; +const ARG_AMOUNT: &str = "amount"; +const ARG_TARGET: &str = "target"; + +#[ignore] +#[test] +fn should_raise_insufficient_payment_when_caller_lacks_minimum_balance() { + let account_1_account_hash = ACCOUNT_1_ADDR; + + let exec_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + TRANSFER_PURSE_TO_ACCOUNT_WASM, + runtime_args! { "target" =>account_1_account_hash, "amount" => U512::from(MAX_PAYMENT - 1) }, + ) + .build(); + + let mut builder = InMemoryWasmTestBuilder::default(); + + let _response = builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .expect_success() + .commit() + .get_exec_response(0) + .expect("there should be a response") + .to_owned(); + + let account_1_request = + ExecuteRequestBuilder::standard(ACCOUNT_1_ADDR, REVERT_WASM, RuntimeArgs::default()) + .build(); + + let account_1_response = builder + .exec(account_1_request) + .commit() + .get_exec_response(1) + .expect("there should be a response"); + + let error_message = utils::get_error_message(account_1_response); + + assert!( + error_message.contains("InsufficientPayment"), + "expected insufficient payment, got: {}", + error_message + ); + + let expected_transfers_count = 0; + let transforms = builder.get_transforms(); + let transform = &transforms[1]; + + assert_eq!( + transform.len(), + expected_transfers_count, + "there should be no transforms if the account main purse has less than max payment" + ); +} + +#[cfg(feature = "use-system-contracts")] +#[ignore] +#[test] +fn should_raise_insufficient_payment_when_payment_code_does_not_pay_enough() { + let account_1_account_hash = ACCOUNT_1_ADDR; + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_session_code( + TRANSFER_PURSE_TO_ACCOUNT_WASM, + runtime_args! { "target" =>account_1_account_hash, "amount" => U512::from(1) }, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => U512::from(1)}) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit(); + + let modified_balance = builder.get_purse_balance( + builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account") + .main_purse(), + ); + let reward_balance = get_pos_rewards_purse_balance(&builder); + + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + let expected_reward_balance: U512 = U512::from(MAX_PAYMENT); + + assert_eq!( + modified_balance, + initial_balance - expected_reward_balance, + "modified balance is incorrect" + ); + + assert_eq!( + reward_balance, expected_reward_balance, + "reward balance is incorrect" + ); + + assert_eq!( + initial_balance, + (modified_balance + reward_balance), + "no net resources should be gained or lost post-distribution" + ); + + let response = builder + .get_exec_response(0) + .expect("there should be a response"); + + let execution_result = utils::get_success_result(response); + let error_message = format!( + "{}", + execution_result.as_error().expect("should have error") + ); + + assert_eq!( + error_message, "Insufficient payment", + "expected insufficient payment" + ); +} + +#[cfg(feature = "use-system-contracts")] +#[ignore] +#[test] +fn should_raise_insufficient_payment_error_when_out_of_gas() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let payment_purse_amount: U512 = U512::from(1); + let transferred_amount = U512::from(1); + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => payment_purse_amount}) + .with_session_code( + TRANSFER_PURSE_TO_ACCOUNT_WASM, + runtime_args! { ARG_TARGET => account_1_account_hash, ARG_AMOUNT => transferred_amount } + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + let expected_reward_balance: U512 = U512::from(MAX_PAYMENT); + + let modified_balance = builder.get_purse_balance( + builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account") + .main_purse(), + ); + let reward_balance = get_pos_rewards_purse_balance(&builder); + + assert_eq!( + modified_balance, + initial_balance - expected_reward_balance, + "modified balance is incorrect" + ); + + assert_eq!( + reward_balance, expected_reward_balance, + "reward balance is incorrect" + ); + + assert_eq!( + initial_balance, + (modified_balance + reward_balance), + "no net resources should be gained or lost post-distribution" + ); + + let response = builder + .get_exec_response(0) + .expect("there should be a response"); + + let execution_result = utils::get_success_result(response); + let error_message = format!( + "{}", + execution_result.as_error().expect("should have error") + ); + + assert_eq!( + error_message, "Insufficient payment", + "expected insufficient payment" + ); +} + +#[ignore] +#[test] +fn should_forward_payment_execution_runtime_error() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let transferred_amount = U512::from(1); + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_payment_code(REVERT_WASM, RuntimeArgs::default()) + .with_session_code( + TRANSFER_PURSE_TO_ACCOUNT_WASM, + runtime_args! { ARG_TARGET => account_1_account_hash, ARG_AMOUNT => transferred_amount } + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + let expected_reward_balance: U512 = U512::from(MAX_PAYMENT); + + let modified_balance = builder.get_purse_balance( + builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account") + .main_purse(), + ); + let reward_balance = get_pos_rewards_purse_balance(&builder); + + assert_eq!( + modified_balance, + initial_balance - expected_reward_balance, + "modified balance is incorrect" + ); + + assert_eq!( + reward_balance, expected_reward_balance, + "reward balance is incorrect" + ); + + assert_eq!( + initial_balance, + (modified_balance + reward_balance), + "no net resources should be gained or lost post-distribution" + ); + + let response = builder + .get_exec_response(0) + .expect("there should be a response"); + + let execution_result = utils::get_success_result(response); + let error = execution_result.as_error().expect("should have error"); + assert_matches!( + error, + Error::Exec(execution::Error::Revert(ApiError::User(100))) + ); +} + +#[ignore] +#[test] +fn should_forward_payment_execution_gas_limit_error() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let transferred_amount = U512::from(1); + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_payment_code(ENDLESS_LOOP_WASM, RuntimeArgs::default()) + .with_session_code( + TRANSFER_PURSE_TO_ACCOUNT_WASM, + runtime_args! { ARG_TARGET => account_1_account_hash, ARG_AMOUNT => transferred_amount } + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + let expected_reward_balance: U512 = U512::from(MAX_PAYMENT); + + let modified_balance = builder.get_purse_balance( + builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account") + .main_purse(), + ); + let reward_balance = get_pos_rewards_purse_balance(&builder); + + assert_eq!( + modified_balance, + initial_balance - expected_reward_balance, + "modified balance is incorrect" + ); + + assert_eq!( + reward_balance, expected_reward_balance, + "reward balance is incorrect" + ); + + assert_eq!( + initial_balance, + (modified_balance + reward_balance), + "no net resources should be gained or lost post-distribution" + ); + + let response = builder + .get_exec_response(0) + .expect("there should be a response"); + + let execution_result = utils::get_success_result(response); + let error = execution_result.as_error().expect("should have error"); + assert_matches!(error, Error::Exec(execution::Error::GasLimit)); +} + +#[ignore] +#[test] +fn should_run_out_of_gas_when_session_code_exceeds_gas_limit() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let payment_purse_amount = 10_000_000; + let transferred_amount = 1; + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount)}) + .with_session_code( + ENDLESS_LOOP_WASM, + runtime_args! { "target" => account_1_account_hash, "amount" => U512::from(transferred_amount) }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + let transfer_result = builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + let response = transfer_result + .builder() + .get_exec_response(0) + .expect("there should be a response"); + + let execution_result = utils::get_success_result(response); + let error = execution_result.as_error().expect("should have error"); + assert_matches!(error, Error::Exec(execution::Error::GasLimit)); +} + +#[ignore] +#[test] +fn should_correctly_charge_when_session_code_runs_out_of_gas() { + let payment_purse_amount = 10_000_000; + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_empty_payment_bytes( + runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount)}, + ) + .with_session_code(ENDLESS_LOOP_WASM, RuntimeArgs::default()) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get genesis account"); + let modified_balance: U512 = builder.get_purse_balance(default_account.main_purse()); + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + + assert_ne!( + modified_balance, initial_balance, + "balance should be less than initial balance" + ); + + let response = builder + .get_exec_response(0) + .expect("there should be a response"); + + let success_result = utils::get_success_result(&response); + let gas = success_result.cost(); + let motes = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + + let tally = motes.value() + modified_balance; + + assert_eq!( + initial_balance, tally, + "no net resources should be gained or lost post-distribution" + ); + + let execution_result = utils::get_success_result(response); + let error = execution_result.as_error().expect("should have error"); + assert_matches!(error, Error::Exec(execution::Error::GasLimit)); +} + +#[ignore] +#[test] +fn should_correctly_charge_when_session_code_fails() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let payment_purse_amount = 10_000_000; + let transferred_amount = 1; + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount)}) + .with_session_code( + REVERT_WASM, + runtime_args! { "target" => account_1_account_hash, "amount" => U512::from(transferred_amount) }, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .commit() + .finish(); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get genesis account"); + let modified_balance: U512 = builder.get_purse_balance(default_account.main_purse()); + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + + assert_ne!( + modified_balance, initial_balance, + "balance should be less than initial balance" + ); + + let response = builder + .get_exec_response(0) + .expect("there should be a response") + .clone(); + + let success_result = utils::get_success_result(&response); + let gas = success_result.cost(); + let motes = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + let tally = motes.value() + modified_balance; + + assert_eq!( + initial_balance, tally, + "no net resources should be gained or lost post-distribution" + ); +} + +#[ignore] +#[test] +fn should_correctly_charge_when_session_code_succeeds() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let payment_purse_amount = 10_000_000; + let transferred_amount = 1; + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_deploy_hash([1; 32]) + .with_session_code( + TRANSFER_PURSE_TO_ACCOUNT_WASM, + runtime_args! { "target" => account_1_account_hash, "amount" => U512::from(transferred_amount) }, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount)}) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(exec_request) + .expect_success() + .commit() + .finish(); + + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get genesis account"); + let modified_balance: U512 = builder.get_purse_balance(default_account.main_purse()); + let initial_balance: U512 = U512::from(DEFAULT_ACCOUNT_INITIAL_BALANCE); + + assert_ne!( + modified_balance, initial_balance, + "balance should be less than initial balance" + ); + + let response = builder + .get_exec_response(0) + .expect("there should be a response") + .clone(); + + let success_result = utils::get_success_result(&response); + let gas = success_result.cost(); + let motes = Motes::from_gas(gas, CONV_RATE).expect("should have motes"); + let total = motes.value() + U512::from(transferred_amount); + let tally = total + modified_balance; + + assert_eq!( + initial_balance, tally, + "no net resources should be gained or lost post-distribution" + ); + assert_eq!( + initial_balance, tally, + "no net resources should be gained or lost post-distribution" + ) +} + +fn get_pos_purse_by_name(builder: &InMemoryWasmTestBuilder, purse_name: &str) -> Option { + let pos_contract = builder.get_pos_contract(); + + pos_contract + .named_keys() + .get(purse_name) + .and_then(Key::as_uref) + .cloned() +} + +fn get_pos_rewards_purse_balance(builder: &InMemoryWasmTestBuilder) -> U512 { + let purse = + get_pos_purse_by_name(builder, POS_REWARDS_PURSE).expect("should find PoS payment purse"); + builder.get_purse_balance(purse) +} + +#[ignore] +#[test] +fn should_finalize_to_rewards_purse() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let payment_purse_amount = 10_000_000; + let transferred_amount = 1; + + let exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code( + TRANSFER_PURSE_TO_ACCOUNT_WASM, + runtime_args! { "target" => account_1_account_hash, "amount" => U512::from(transferred_amount) }, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount)}) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([1; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let rewards_purse_balance = get_pos_rewards_purse_balance(&builder); + assert!(rewards_purse_balance.is_zero()); + + builder.exec(exec_request).expect_success().commit(); + + let rewards_purse_balance = get_pos_rewards_purse_balance(&builder); + assert!(!rewards_purse_balance.is_zero()); +} + +#[ignore] +#[test] +fn independent_standard_payments_should_not_write_the_same_keys() { + let account_1_account_hash = ACCOUNT_1_ADDR; + let payment_purse_amount = 10_000_000; + let transfer_amount = 10_000_000; + + let mut builder = InMemoryWasmTestBuilder::default(); + + let setup_exec_request = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code( + TRANSFER_PURSE_TO_ACCOUNT_WASM, + runtime_args! { "target" => account_1_account_hash, "amount" => U512::from(transfer_amount) }, + ) + .with_empty_payment_bytes(runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount)}) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([1; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + // create another account via transfer + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(setup_exec_request) + .expect_success() + .commit(); + + let exec_request_from_genesis = { + let deploy = DeployItemBuilder::new() + .with_address(DEFAULT_ACCOUNT_ADDR) + .with_session_code(DO_NOTHING_WASM, RuntimeArgs::default()) + .with_empty_payment_bytes( + runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount)}, + ) + .with_authorization_keys(&[DEFAULT_ACCOUNT_KEY]) + .with_deploy_hash([2; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + let exec_request_from_account_1 = { + let deploy = DeployItemBuilder::new() + .with_address(ACCOUNT_1_ADDR) + .with_session_code(DO_NOTHING_WASM, RuntimeArgs::default()) + .with_empty_payment_bytes( + runtime_args! { ARG_AMOUNT => U512::from(payment_purse_amount)}, + ) + .with_authorization_keys(&[account_1_account_hash]) + .with_deploy_hash([1; 32]) + .build(); + + ExecuteRequestBuilder::new().push_deploy(deploy).build() + }; + + // run two independent deploys + builder + .exec(exec_request_from_genesis) + .expect_success() + .commit() + .exec(exec_request_from_account_1) + .expect_success() + .commit(); + + let transforms = builder.get_transforms(); + let transforms_from_genesis = &transforms[1]; + let transforms_from_account_1 = &transforms[2]; + + // confirm the two deploys have no overlapping writes + let common_write_keys = transforms_from_genesis.keys().filter(|k| { + match ( + transforms_from_genesis.get(k), + transforms_from_account_1.get(k), + ) { + (Some(Transform::Write(_)), Some(Transform::Write(_))) => true, + _ => false, + } + }); + + assert_eq!(common_write_keys.count(), 0); +} diff --git a/grpc/tests/src/test/system_contracts/standard_payment_install.rs b/grpc/tests/src/test/system_contracts/standard_payment_install.rs new file mode 100644 index 0000000000..eaa1009980 --- /dev/null +++ b/grpc/tests/src/test/system_contracts/standard_payment_install.rs @@ -0,0 +1,44 @@ +use engine_test_support::{ + internal::{ + exec_with_return, WasmTestBuilder, DEFAULT_BLOCK_TIME, DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::engine_state::EngineConfig; +use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use types::{runtime_args, ContractHash, RuntimeArgs}; + +const DEPLOY_HASH_1: [u8; 32] = [1u8; 32]; + +#[ignore] +#[test] +fn should_run_standard_payment_install_contract() { + let mut builder = WasmTestBuilder::default(); + let engine_config = + EngineConfig::new().with_use_system_contracts(cfg!(feature = "use-system-contracts")); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let (standard_payment_hash, ret_urefs, effect): (ContractHash, _, _) = exec_with_return::exec( + engine_config, + &mut builder, + DEFAULT_ACCOUNT_ADDR, + "standard_payment_install.wasm", + DEFAULT_BLOCK_TIME, + DEPLOY_HASH_1, + "install", + runtime_args! {}, + vec![], + ) + .expect("should run successfully"); + + // should return a uref + assert_eq!(ret_urefs.len(), 0); + + // should have written a contract under that uref + match effect.transforms.get(&standard_payment_hash.into()) { + Some(Transform::Write(StoredValue::Contract(_))) => (), + + _ => panic!("Expected contract to be written under the key"), + } +} diff --git a/grpc/tests/src/test/system_contracts/upgrade.rs b/grpc/tests/src/test/system_contracts/upgrade.rs new file mode 100644 index 0000000000..800d8fd240 --- /dev/null +++ b/grpc/tests/src/test/system_contracts/upgrade.rs @@ -0,0 +1,644 @@ +use engine_grpc_server::engine_server::ipc::DeployCode; +use engine_test_support::internal::{ + utils, InMemoryWasmTestBuilder, UpgradeRequestBuilder, DEFAULT_RUN_GENESIS_REQUEST, + DEFAULT_WASM_COSTS, +}; +#[cfg(feature = "use-system-contracts")] +use engine_test_support::{internal::ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; +use node::contract_core::engine_state::{upgrade::ActivationPoint, Error}; +use node::contract_shared::wasm_costs::WasmCosts; +#[cfg(feature = "use-system-contracts")] +use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use types::ProtocolVersion; +#[cfg(feature = "use-system-contracts")] +use types::{runtime_args, CLValue, Key, RuntimeArgs, U512}; + +const PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::V1_0_0; +const DEFAULT_ACTIVATION_POINT: ActivationPoint = 1; +const MODIFIED_SYSTEM_UPGRADER_CONTRACT_NAME: &str = "modified_system_upgrader.wasm"; +#[cfg(feature = "use-system-contracts")] +const MODIFIED_MINT_CALLER_CONTRACT_NAME: &str = "modified_mint_caller.wasm"; +#[cfg(feature = "use-system-contracts")] +const PAYMENT_AMOUNT: u64 = 200_000_000; +#[cfg(feature = "use-system-contracts")] +const ARG_TARGET: &str = "target"; + +fn get_upgraded_wasm_costs() -> WasmCosts { + WasmCosts { + regular: 1, + div: 1, + mul: 1, + mem: 1, + initial_mem: 4096, + grow_mem: 8192, + memcpy: 1, + max_stack_height: 64 * 1024, + opcodes_mul: 3, + opcodes_div: 8, + } +} + +#[ignore] +#[test] +fn should_upgrade_only_protocol_version() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let sem_ver = PROTOCOL_VERSION.value(); + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major, sem_ver.minor, sem_ver.patch + 1); + + let mut upgrade_request = { + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(new_protocol_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!(upgrade_response.has_success(), "expected success"); + + let upgraded_wasm_costs = builder + .get_engine_state() + .wasm_costs(new_protocol_version) + .expect("should have result") + .expect("should have costs"); + + assert_eq!( + *DEFAULT_WASM_COSTS, upgraded_wasm_costs, + "upgraded costs should equal original costs" + ); +} + +#[cfg(feature = "use-system-contracts")] +#[ignore] +#[test] +fn should_upgrade_system_contract() { + let mut builder = InMemoryWasmTestBuilder::default(); + + let new_protocol_version = ProtocolVersion::from_parts(2, 0, 0); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let mut upgrade_request = { + let bytes = utils::read_wasm_file_bytes(MODIFIED_SYSTEM_UPGRADER_CONTRACT_NAME); + let mut installer_code = DeployCode::new(); + installer_code.set_code(bytes); + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(new_protocol_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .with_installer_code(installer_code) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!( + upgrade_response.has_success(), + "upgrade_response expected success" + ); + + let exec_request = { + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &MODIFIED_MINT_CALLER_CONTRACT_NAME, + runtime_args! { "amount" => U512::from(PAYMENT_AMOUNT) }, + ) + .with_protocol_version(new_protocol_version) + .build() + }; + + builder.exec(exec_request).expect_success(); + + let transforms = builder.get_transforms(); + let transform = &transforms[0]; + + let new_keys = if let Some(Transform::AddKeys(keys)) = + transform.get(&Key::Account(DEFAULT_ACCOUNT_ADDR)) + { + keys + } else { + panic!( + "expected AddKeys transform for given key but received {:?}", + transforms[0] + ); + }; + + let version_uref = new_keys + .get("output_version") + .expect("version_uref should exist"); + + builder.commit(); + + let version_value: StoredValue = builder + .query(None, *version_uref, &[]) + .expect("should find version_uref value"); + + assert_eq!( + version_value, + StoredValue::CLValue(CLValue::from_t("1.1.0".to_string()).unwrap()), + "expected new version endpoint output" + ); +} + +#[cfg(feature = "use-system-contracts")] +#[ignore] +#[test] +fn should_upgrade_system_contract_on_patch_bump() { + let mut builder = InMemoryWasmTestBuilder::default(); + + let sem_ver = PROTOCOL_VERSION.value(); + + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major, sem_ver.minor, sem_ver.patch + 123); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let mut upgrade_request = { + let bytes = utils::read_wasm_file_bytes(MODIFIED_SYSTEM_UPGRADER_CONTRACT_NAME); + let mut installer_code = DeployCode::new(); + installer_code.set_code(bytes); + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(new_protocol_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .with_installer_code(installer_code) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!( + upgrade_response.has_success(), + "upgrade_response expected success" + ); + + let exec_request = { + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &MODIFIED_MINT_CALLER_CONTRACT_NAME, + runtime_args! { ARG_TARGET => U512::from(PAYMENT_AMOUNT) }, + ) + .with_protocol_version(new_protocol_version) + .build() + }; + + builder.exec(exec_request).expect_success(); + + let transforms = builder.get_transforms(); + let transform = &transforms[0]; + + let new_keys = if let Some(Transform::AddKeys(keys)) = + transform.get(&Key::Account(DEFAULT_ACCOUNT_ADDR)) + { + keys + } else { + panic!( + "expected AddKeys transform for given key but received {:?}", + transforms[0] + ); + }; + + let version_uref = new_keys + .get("output_version") + .expect("version_uref should exist"); + + builder.commit(); + + let version_value = builder + .query(None, *version_uref, &[]) + .expect("should find version_uref value"); + + assert_eq!( + version_value, + StoredValue::CLValue(CLValue::from_t("1.1.0").unwrap()), + "expected new version endpoint output" + ); +} + +#[cfg(feature = "use-system-contracts")] +#[ignore] +#[test] +fn should_upgrade_system_contract_on_minor_bump() { + let mut builder = InMemoryWasmTestBuilder::default(); + + let sem_ver = PROTOCOL_VERSION.value(); + + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major, sem_ver.minor + 1, sem_ver.patch); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let mut upgrade_request = { + let bytes = utils::read_wasm_file_bytes(MODIFIED_SYSTEM_UPGRADER_CONTRACT_NAME); + let mut installer_code = DeployCode::new(); + installer_code.set_code(bytes); + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(new_protocol_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .with_installer_code(installer_code) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!( + upgrade_response.has_success(), + "upgrade_response expected success" + ); + + let exec_request = { + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &MODIFIED_MINT_CALLER_CONTRACT_NAME, + runtime_args! {ARG_TARGET => U512::from(PAYMENT_AMOUNT) }, + ) + .with_protocol_version(new_protocol_version) + .build() + }; + + builder.exec(exec_request).expect_success(); + + let transforms = builder.get_transforms(); + let transform = &transforms[0]; + + let new_keys = if let Some(Transform::AddKeys(keys)) = + transform.get(&Key::Account(DEFAULT_ACCOUNT_ADDR)) + { + keys + } else { + panic!( + "expected AddKeys transform for given key but received {:?}", + transforms[0] + ); + }; + + let version_uref = new_keys + .get("output_version") + .expect("version_uref should exist"); + + builder.commit(); + + let version_value = builder + .query(None, *version_uref, &[]) + .expect("should find version_uref value"); + + assert_eq!( + version_value, + StoredValue::CLValue(CLValue::from_t("1.1.0").unwrap()), + "expected new version endpoint output" + ); +} + +#[ignore] +#[test] +fn should_allow_only_wasm_costs_patch_version() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let sem_ver = PROTOCOL_VERSION.value(); + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major, sem_ver.minor, sem_ver.patch + 2); + + let new_costs = get_upgraded_wasm_costs(); + + let mut upgrade_request = { + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(new_protocol_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .with_new_costs(new_costs) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!(upgrade_response.has_success(), "expected success"); + + let upgraded_wasm_costs = builder + .get_engine_state() + .wasm_costs(new_protocol_version) + .expect("should have result") + .expect("should have upgraded costs"); + + assert_eq!( + new_costs, upgraded_wasm_costs, + "upgraded costs should equal new costs" + ); +} + +#[ignore] +#[test] +fn should_allow_only_wasm_costs_minor_version() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let sem_ver = PROTOCOL_VERSION.value(); + let new_protocol_version = + ProtocolVersion::from_parts(sem_ver.major, sem_ver.minor + 1, sem_ver.patch); + + let new_costs = get_upgraded_wasm_costs(); + + let mut upgrade_request = { + let bytes = utils::read_wasm_file_bytes(MODIFIED_SYSTEM_UPGRADER_CONTRACT_NAME); + let mut installer_code = DeployCode::new(); + installer_code.set_code(bytes); + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(new_protocol_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .with_new_costs(new_costs) + .with_installer_code(installer_code) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!(upgrade_response.has_success(), "expected success"); + + let upgraded_wasm_costs = builder + .get_engine_state() + .wasm_costs(new_protocol_version) + .expect("should have result") + .expect("should have upgraded costs"); + + assert_eq!( + new_costs, upgraded_wasm_costs, + "upgraded costs should equal new costs" + ); +} + +#[cfg(feature = "use-system-contracts")] +#[ignore] +#[test] +fn should_upgrade_system_contract_and_wasm_costs_major() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let new_protocol_version = ProtocolVersion::from_parts(2, 0, 0); + + let new_costs = get_upgraded_wasm_costs(); + + let mut upgrade_request = { + let bytes = utils::read_wasm_file_bytes(MODIFIED_SYSTEM_UPGRADER_CONTRACT_NAME); + let mut installer_code = DeployCode::new(); + installer_code.set_code(bytes); + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(new_protocol_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .with_installer_code(installer_code) + .with_new_costs(new_costs) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!(upgrade_response.has_success(), "expected success"); + + let exec_request = { + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &MODIFIED_MINT_CALLER_CONTRACT_NAME, + runtime_args! {ARG_TARGET => U512::from(PAYMENT_AMOUNT) }, + ) + .with_protocol_version(new_protocol_version) + .build() + }; + + builder.exec(exec_request).expect_success(); + + let transforms = builder.get_transforms(); + let transform = &transforms[0]; + + let new_keys = if let Some(Transform::AddKeys(keys)) = + transform.get(&Key::Account(DEFAULT_ACCOUNT_ADDR)) + { + keys + } else { + panic!( + "expected AddKeys transform for given key but received {:?}", + transforms[0] + ); + }; + + let version_uref = new_keys + .get("output_version") + .expect("version_uref should exist"); + + builder.commit(); + + let version_value: StoredValue = builder + .query(None, *version_uref, &[]) + .expect("should find version_uref value"); + + assert_eq!( + version_value, + StoredValue::CLValue(CLValue::from_t("1.1.0".to_string()).unwrap()), + "expected new version endpoint output" + ); + + let upgraded_wasm_costs = builder + .get_engine_state() + .wasm_costs(new_protocol_version) + .expect("should have result") + .expect("should have upgraded costs"); + + assert_eq!( + new_costs, upgraded_wasm_costs, + "upgraded costs should equal new costs" + ); +} + +#[ignore] +#[test] +fn should_not_downgrade() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let new_protocol_version = ProtocolVersion::from_parts(2, 0, 0); + + let mut upgrade_request = { + let bytes = utils::read_wasm_file_bytes(MODIFIED_SYSTEM_UPGRADER_CONTRACT_NAME); + let mut installer_code = DeployCode::new(); + installer_code.set_code(bytes); + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(new_protocol_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .with_installer_code(installer_code) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!( + upgrade_response.has_success(), + "expected success but received {:?}", + upgrade_response + ); + + let upgraded_wasm_costs = builder + .get_engine_state() + .wasm_costs(new_protocol_version) + .expect("should have result") + .expect("should have costs"); + + assert_eq!( + *DEFAULT_WASM_COSTS, upgraded_wasm_costs, + "upgraded costs should equal original costs" + ); + + let mut downgrade_request = { + UpgradeRequestBuilder::new() + .with_current_protocol_version(new_protocol_version) + .with_new_protocol_version(PROTOCOL_VERSION) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut downgrade_request); + + let upgrade_response = builder + .get_upgrade_response(1) + .expect("should have response"); + + assert!(!upgrade_response.has_success(), "expected failure"); +} + +#[ignore] +#[test] +fn should_not_skip_major_versions() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let sem_ver = PROTOCOL_VERSION.value(); + + let invalid_version = + ProtocolVersion::from_parts(sem_ver.major + 2, sem_ver.minor, sem_ver.patch); + + let mut upgrade_request = { + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(invalid_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!(!upgrade_response.has_success(), "expected failure"); +} + +#[ignore] +#[test] +fn should_not_skip_minor_versions() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let sem_ver = PROTOCOL_VERSION.value(); + + let invalid_version = + ProtocolVersion::from_parts(sem_ver.major, sem_ver.minor + 2, sem_ver.patch); + + let mut upgrade_request = { + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(invalid_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!(!upgrade_response.has_success(), "expected failure"); +} + +#[ignore] +#[test] +fn should_fail_major_upgrade_without_installer() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + let sem_ver = PROTOCOL_VERSION.value(); + + let invalid_version = + ProtocolVersion::from_parts(sem_ver.major + 1, sem_ver.minor, sem_ver.patch); + + let mut upgrade_request = { + UpgradeRequestBuilder::new() + .with_current_protocol_version(PROTOCOL_VERSION) + .with_new_protocol_version(invalid_version) + .with_activation_point(DEFAULT_ACTIVATION_POINT) + .build() + }; + + builder.upgrade_with_upgrade_request(&mut upgrade_request); + + let upgrade_response = builder + .get_upgrade_response(0) + .expect("should have response"); + + assert!( + upgrade_response.has_failed_deploy(), + "should have failed deploy" + ); + + let failed_deploy = upgrade_response.get_failed_deploy(); + assert_eq!( + failed_deploy.message, + Error::InvalidUpgradeConfig.to_string() + ); +} diff --git a/grpc/tests/src/test/upgrade.rs b/grpc/tests/src/test/upgrade.rs new file mode 100644 index 0000000000..fa2a34af25 --- /dev/null +++ b/grpc/tests/src/test/upgrade.rs @@ -0,0 +1,575 @@ +use engine_test_support::{ + internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_shared::stored_value::StoredValue; +use types::{ + contracts::{ContractVersion, CONTRACT_INITIAL_VERSION}, + runtime_args, CLValue, ContractPackageHash, RuntimeArgs, +}; + +const DO_NOTHING_STORED_CONTRACT_NAME: &str = "do_nothing_stored"; +const DO_NOTHING_STORED_UPGRADER_CONTRACT_NAME: &str = "do_nothing_stored_upgrader"; +const DO_NOTHING_STORED_CALLER_CONTRACT_NAME: &str = "do_nothing_stored_caller"; +const ENTRY_FUNCTION_NAME: &str = "delegate"; +const DO_NOTHING_PACKAGE_HASH_KEY_NAME: &str = "do_nothing_package_hash"; +const DO_NOTHING_HASH_KEY_NAME: &str = "do_nothing_hash"; +const INITIAL_VERSION: ContractVersion = CONTRACT_INITIAL_VERSION; +const UPGRADED_VERSION: ContractVersion = INITIAL_VERSION + 1; +const PURSE_NAME_ARG_NAME: &str = "purse_name"; +const PURSE_1: &str = "purse_1"; +const METHOD_REMOVE: &str = "remove"; +const VERSION: &str = "version"; +const PURSE_HOLDER_STORED_CALLER_CONTRACT_NAME: &str = "purse_holder_stored_caller"; +const PURSE_HOLDER_STORED_CONTRACT_NAME: &str = "purse_holder_stored"; +const PURSE_HOLDER_STORED_UPGRADER_CONTRACT_NAME: &str = "purse_holder_stored_upgrader"; +const HASH_KEY_NAME: &str = "purse_holder"; +const TOTAL_PURSES: usize = 3; +const PURSE_NAME: &str = "purse_name"; +const ENTRY_POINT_NAME: &str = "entry_point"; +const ENTRY_POINT_ADD: &str = "add_named_purse"; +const ARG_CONTRACT_PACKAGE: &str = "contract_package"; +const ARG_VERSION: &str = "version"; +const ARG_NEW_PURSE_NAME: &str = "new_purse_name"; + +/// Performs define and execution of versioned contracts, calling them directly from hash +#[ignore] +#[test] +fn should_upgrade_do_nothing_to_do_something_version_hash_call() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + // Create contract package and store contract ver: 1.0.0 with "delegate" entry function + { + let exec_request = { + let contract_name = format!("{}.wasm", DO_NOTHING_STORED_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + RuntimeArgs::default(), + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + // Calling initial version from contract package hash, should have no effects + { + let exec_request = { + ExecuteRequestBuilder::versioned_contract_call_by_hash_key_name( + DEFAULT_ACCOUNT_ADDR, + DO_NOTHING_PACKAGE_HASH_KEY_NAME, + Some(INITIAL_VERSION), + ENTRY_FUNCTION_NAME, + RuntimeArgs::new(), + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + let account_1 = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get account 1"); + + assert!( + account_1.named_keys().get(PURSE_1).is_none(), + "purse should not exist", + ); + + // Upgrade version having call to create_purse_01 + { + let exec_request = { + let contract_name = format!("{}.wasm", DO_NOTHING_STORED_UPGRADER_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + RuntimeArgs::default(), + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + // Calling upgraded version, expecting purse creation + { + let args = runtime_args! { + PURSE_NAME_ARG_NAME => PURSE_1, + }; + let exec_request = { + ExecuteRequestBuilder::versioned_contract_call_by_hash_key_name( + DEFAULT_ACCOUNT_ADDR, + DO_NOTHING_PACKAGE_HASH_KEY_NAME, + Some(UPGRADED_VERSION), + ENTRY_FUNCTION_NAME, + args, + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + let account_1 = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get account 1"); + + assert!( + account_1.named_keys().get(PURSE_1).is_some(), + "purse should exist", + ); +} + +/// Performs define and execution of versioned contracts, calling them from a contract +#[ignore] +#[test] +fn should_upgrade_do_nothing_to_do_something_contract_call() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&*DEFAULT_RUN_GENESIS_REQUEST); + + // Create contract package and store contract ver: 1.0.0 + { + let exec_request = { + let contract_name = format!("{}.wasm", DO_NOTHING_STORED_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + RuntimeArgs::default(), + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + let account_1 = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get account 1"); + + account_1 + .named_keys() + .get(DO_NOTHING_HASH_KEY_NAME) + .expect("should have key of do_nothing_hash") + .into_hash() + .expect("should have into hash"); + + let stored_contract_package_hash = account_1 + .named_keys() + .get(DO_NOTHING_PACKAGE_HASH_KEY_NAME) + .expect("should have key of do_nothing_hash") + .into_hash() + .expect("should have hash"); + + // Calling initial stored version from contract package hash, should have no effects + { + let contract_name = format!("{}.wasm", DO_NOTHING_STORED_CALLER_CONTRACT_NAME); + let args = runtime_args! { + ARG_CONTRACT_PACKAGE => stored_contract_package_hash, + ARG_VERSION => INITIAL_VERSION, + ARG_NEW_PURSE_NAME => PURSE_1, + }; + let exec_request = + { ExecuteRequestBuilder::standard(DEFAULT_ACCOUNT_ADDR, &contract_name, args).build() }; + + builder.exec(exec_request).expect_success().commit(); + } + + let account_1 = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get account 1"); + + assert!( + account_1.named_keys().get(PURSE_1).is_none(), + "purse should not exist", + ); + + // Upgrade stored contract to version: 2.0.0, having call to create_purse_01 + { + let exec_request = { + let contract_name = format!("{}.wasm", DO_NOTHING_STORED_UPGRADER_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + RuntimeArgs::default(), + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + let stored_contract_package_hash = account_1 + .named_keys() + .get(DO_NOTHING_PACKAGE_HASH_KEY_NAME) + .expect("should have key of do_nothing_hash") + .into_hash() + .expect("should have hash"); + + // Calling upgraded stored version, expecting purse creation + { + let contract_name = format!("{}.wasm", DO_NOTHING_STORED_CALLER_CONTRACT_NAME); + let args = runtime_args! { + ARG_CONTRACT_PACKAGE => stored_contract_package_hash, + ARG_VERSION => UPGRADED_VERSION, + ARG_NEW_PURSE_NAME => PURSE_1, + }; + + let exec_request = + { ExecuteRequestBuilder::standard(DEFAULT_ACCOUNT_ADDR, &contract_name, args).build() }; + + builder.exec(exec_request).expect_success().commit(); + } + + let account_1 = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should get account 1"); + + assert!( + account_1.named_keys().get(PURSE_1).is_some(), + "purse should exist", + ); +} + +#[ignore] +#[test] +fn should_be_able_to_observe_state_transition_across_upgrade() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + // store do-nothing-stored + { + let exec_request = { + let contract_name = format!("{}.wasm", PURSE_HOLDER_STORED_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + RuntimeArgs::default(), + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + assert!( + account.named_keys().contains_key(VERSION), + "version uref should exist on install" + ); + + let stored_package_hash: ContractPackageHash = account + .named_keys() + .get(HASH_KEY_NAME) + .expect("should have stored uref") + .into_hash() + .expect("should have hash"); + + // verify version before upgrade + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + let version = *account + .named_keys() + .get(VERSION) + .expect("version uref should exist"); + + let original_version = builder + .query(None, version, &[]) + .expect("version should exist"); + + assert_eq!( + original_version, + StoredValue::CLValue(CLValue::from_t("1.0.0".to_string()).unwrap()), + "should be original version" + ); + + // upgrade contract + { + let exec_request = { + let contract_name = format!("{}.wasm", PURSE_HOLDER_STORED_UPGRADER_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + runtime_args! { + ARG_CONTRACT_PACKAGE => stored_package_hash, + }, + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + // version should change after upgrade + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + let version = *account + .named_keys() + .get(VERSION) + .expect("version key should exist"); + + let upgraded_version = builder + .query(None, version, &[]) + .expect("version should exist"); + + assert_eq!( + upgraded_version, + StoredValue::CLValue(CLValue::from_t("1.0.1".to_string()).unwrap()), + "should be original version" + ); +} + +#[ignore] +#[test] +fn should_support_extending_functionality() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + // store do-nothing-stored + { + let exec_request = { + let contract_name = format!("{}.wasm", PURSE_HOLDER_STORED_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + RuntimeArgs::default(), + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + let stored_package_hash = account + .named_keys() + .get(HASH_KEY_NAME) + .expect("should have stored uref") + .into_hash() + .expect("should have hash"); + + let stored_hash = account + .named_keys() + .get(PURSE_HOLDER_STORED_CONTRACT_NAME) + .expect("should have stored uref") + .into_hash() + .expect("should have hash"); + + // call stored contract and persist a known uref before upgrade + { + let exec_request = { + let contract_name = format!("{}.wasm", PURSE_HOLDER_STORED_CALLER_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + runtime_args! { + HASH_KEY_NAME => stored_hash, + ENTRY_POINT_NAME => ENTRY_POINT_ADD, + PURSE_NAME => PURSE_1, + }, + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + // verify known uref actually exists prior to upgrade + let contract = builder + .get_contract(stored_hash) + .expect("should have contract"); + assert!( + contract.named_keys().contains_key(PURSE_1), + "purse uref should exist in contract's named_keys before upgrade" + ); + + // upgrade contract + { + let exec_request = { + let contract_name = format!("{}.wasm", PURSE_HOLDER_STORED_UPGRADER_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + runtime_args! { + ARG_CONTRACT_PACKAGE => stored_package_hash, + }, + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + // verify uref still exists in named_keys after upgrade: + let contract = builder + .get_contract(stored_hash) + .expect("should have contract"); + + assert!( + contract.named_keys().contains_key(PURSE_1), + "PURSE_1 uref should still exist in contract's named_keys after upgrade" + ); + + // Get account again after upgrade to refresh named keys + let account_2 = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + // Get contract again after upgrade + + let stored_hash_2 = account_2 + .named_keys() + .get(PURSE_HOLDER_STORED_CONTRACT_NAME) + .expect("should have stored uref") + .into_hash() + .expect("should have hash"); + assert_ne!(stored_hash, stored_hash_2); + + // call new remove function + { + let exec_request = { + let contract_name = format!("{}.wasm", PURSE_HOLDER_STORED_CALLER_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + runtime_args! { + HASH_KEY_NAME => stored_hash_2, + ENTRY_POINT_NAME => METHOD_REMOVE, + PURSE_NAME => PURSE_1, + }, + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + // verify known urefs no longer include removed purse + let contract = builder + .get_contract(stored_hash_2) + .expect("should have contract"); + + assert!( + !contract.named_keys().contains_key(PURSE_1), + "PURSE_1 uref should no longer exist in contract's named_keys after remove" + ); +} + +#[ignore] +#[test] +fn should_maintain_named_keys_across_upgrade() { + let mut builder = InMemoryWasmTestBuilder::default(); + + builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST); + + // store contract + { + let exec_request = { + let contract_name = format!("{}.wasm", PURSE_HOLDER_STORED_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + RuntimeArgs::default(), + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + let account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("should have account"); + + let stored_hash = account + .named_keys() + .get(PURSE_HOLDER_STORED_CONTRACT_NAME) + .expect("should have stored hash") + .into_hash() + .expect("should have hash"); + + let stored_package_hash = account + .named_keys() + .get(HASH_KEY_NAME) + .expect("should have stored package hash") + .into_hash() + .expect("should have hash"); + + // add several purse urefs to named_keys + for index in 0..TOTAL_PURSES { + let purse_name: &str = &format!("purse_{}", index); + + let exec_request = { + let contract_name = format!("{}.wasm", PURSE_HOLDER_STORED_CALLER_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + runtime_args! { + HASH_KEY_NAME => stored_hash, + ENTRY_POINT_NAME => ENTRY_POINT_ADD, + PURSE_NAME => purse_name, + }, + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + + // verify known uref actually exists prior to upgrade + let contract = builder + .get_contract(stored_hash) + .expect("should have contract"); + assert!( + contract.named_keys().contains_key(purse_name), + "purse uref should exist in contract's named_keys before upgrade" + ); + } + + // upgrade contract + { + let exec_request = { + let contract_name = format!("{}.wasm", PURSE_HOLDER_STORED_UPGRADER_CONTRACT_NAME); + ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + &contract_name, + runtime_args! { + ARG_CONTRACT_PACKAGE => stored_package_hash, + }, + ) + .build() + }; + + builder.exec(exec_request).expect_success().commit(); + } + + // verify all urefs still exist in named_keys after upgrade + let contract = builder + .get_contract(stored_hash) + .expect("should have contract"); + + for index in 0..TOTAL_PURSES { + let purse_name: &str = &format!("purse_{}", index); + assert!( + contract.named_keys().contains_key(purse_name), + format!( + "{} uref should still exist in contract's named_keys after upgrade", + index + ) + ); + } +} diff --git a/grpc/tests/src/test/wasmless_transfer.rs b/grpc/tests/src/test/wasmless_transfer.rs new file mode 100644 index 0000000000..771892330b --- /dev/null +++ b/grpc/tests/src/test/wasmless_transfer.rs @@ -0,0 +1,495 @@ +use lazy_static::lazy_static; + +use engine_test_support::{ + internal::{ + DeployItemBuilder, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_PAYMENT, + DEFAULT_RUN_GENESIS_REQUEST, + }, + DEFAULT_ACCOUNT_ADDR, +}; +use node::contract_core::{engine_state::Error as CoreError, execution::Error as ExecError}; +use types::{ + account::AccountHash, runtime_args, AccessRights, ApiError, Key, RuntimeArgs, URef, U512, +}; + +const CONTRACT_TRANSFER_PURSE_TO_ACCOUNT: &str = "transfer_purse_to_account.wasm"; +const TRANSFER_RESULT_NAMED_KEY: &str = "transfer_result"; + +lazy_static! { + static ref TRANSFER_1_AMOUNT: U512 = U512::from(250_000_000) + 1000; + static ref TRANSFER_2_AMOUNT: U512 = U512::from(750); + static ref TRANSFER_2_AMOUNT_WITH_ADV: U512 = *DEFAULT_PAYMENT + *TRANSFER_2_AMOUNT; + static ref TRANSFER_TOO_MUCH: U512 = U512::from(u64::max_value()); + static ref ACCOUNT_1_INITIAL_BALANCE: U512 = *DEFAULT_PAYMENT; +} + +const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([1u8; 32]); +const ACCOUNT_2_ADDR: AccountHash = AccountHash::new([2u8; 32]); +const ARG_SOURCE: &str = "source"; +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +#[ignore] +#[test] +fn should_transfer_wasmless_account_to_purse() { + transfer_wasmless(WasmlessTransfer::AccountMainPurseToPurse); +} + +#[ignore] +#[test] +fn should_transfer_wasmless_account_to_account() { + transfer_wasmless(WasmlessTransfer::AccountMainPurseToAccountMainPurse); +} + +#[ignore] +#[test] +fn should_transfer_wasmless_account_to_account_by_key() { + transfer_wasmless(WasmlessTransfer::AccountToAccountByKey); +} + +#[ignore] +#[test] +fn should_transfer_wasmless_purse_to_purse() { + transfer_wasmless(WasmlessTransfer::PurseToPurse); +} + +#[ignore] +#[test] +fn should_transfer_wasmless_amount_as_u64() { + transfer_wasmless(WasmlessTransfer::AmountAsU64); +} + +enum WasmlessTransfer { + AccountMainPurseToPurse, + AccountMainPurseToAccountMainPurse, + PurseToPurse, + AccountToAccountByKey, + AmountAsU64, +} + +fn transfer_wasmless(wasmless_transfer: WasmlessTransfer) { + let create_account_2: bool = true; + let mut builder = init_wasmless_transform_builder(create_account_2); + let transfer_amount: U512 = U512::from(1000); + + let account_1_purse = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should get account 1") + .main_purse(); + + let account_2_purse = builder + .get_account(ACCOUNT_2_ADDR) + .expect("should get account 2") + .main_purse(); + + let account_1_starting_balance = builder.get_purse_balance(account_1_purse); + let account_2_starting_balance = builder.get_purse_balance(account_2_purse); + + let runtime_args = match wasmless_transfer { + WasmlessTransfer::AccountMainPurseToPurse => { + runtime_args! { ARG_TARGET => account_2_purse, ARG_AMOUNT => transfer_amount } + } + WasmlessTransfer::AccountMainPurseToAccountMainPurse => { + runtime_args! { ARG_TARGET => ACCOUNT_2_ADDR, ARG_AMOUNT => transfer_amount } + } + WasmlessTransfer::AccountToAccountByKey => { + runtime_args! { ARG_TARGET => types::Key::Account(ACCOUNT_2_ADDR), ARG_AMOUNT => transfer_amount } + } + WasmlessTransfer::PurseToPurse => { + runtime_args! { ARG_SOURCE => account_1_purse, ARG_TARGET => account_2_purse, ARG_AMOUNT => transfer_amount } + } + WasmlessTransfer::AmountAsU64 => { + runtime_args! { ARG_SOURCE => account_1_purse, ARG_TARGET => account_2_purse, ARG_AMOUNT => 1000u64 } + } + }; + + let no_wasm_transfer_request = { + let deploy_item = DeployItemBuilder::new() + .with_address(ACCOUNT_1_ADDR) + .with_empty_payment_bytes(runtime_args! {}) + .with_transfer_args(runtime_args) + .with_authorization_keys(&[ACCOUNT_1_ADDR]) + .build(); + ExecuteRequestBuilder::from_deploy_item(deploy_item).build() + }; + + builder + .exec(no_wasm_transfer_request) + .expect_success() + .commit(); + + assert_eq!( + account_1_starting_balance - transfer_amount, + builder.get_purse_balance(account_1_purse), + "account 1 ending balance incorrect" + ); + assert_eq!( + account_2_starting_balance + transfer_amount, + builder.get_purse_balance(account_2_purse), + "account 2 ending balance incorrect" + ); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_to_self_by_addr() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::TransferToSelfByAddr); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_to_self_by_key() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::TransferToSelfByKey); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_to_self_by_uref() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::TransferToSelfByURef); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_other_account_by_addr() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::OtherSourceAccountByAddr); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_other_account_by_key() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::OtherSourceAccountByKey); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_other_account_by_uref() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::OtherSourceAccountByURef); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_missing_target() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::MissingTarget); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_missing_amount() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::MissingAmount); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_source_uref_nonexistent() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::SourceURefNonexistent); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_target_uref_nonexistent() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::TargetURefNonexistent); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_invalid_source_uref() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::SourceURefNotPurse); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_invalid_target_uref() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::TargetURefNotPurse); +} + +#[ignore] +#[test] +fn should_not_transfer_wasmless_other_purse_to_self_purse() { + invalid_transfer_wasmless(InvalidWasmlessTransfer::OtherPurseToSelfPurse); +} + +enum InvalidWasmlessTransfer { + TransferToSelfByAddr, + TransferToSelfByKey, + TransferToSelfByURef, + OtherSourceAccountByAddr, + OtherSourceAccountByKey, + OtherSourceAccountByURef, + MissingTarget, + MissingAmount, + SourceURefNotPurse, + TargetURefNotPurse, + SourceURefNonexistent, + TargetURefNonexistent, + OtherPurseToSelfPurse, +} + +fn invalid_transfer_wasmless(invalid_wasmless_transfer: InvalidWasmlessTransfer) { + let create_account_2: bool = true; + let mut builder = init_wasmless_transform_builder(create_account_2); + let transfer_amount: U512 = U512::from(1000); + + let (addr, runtime_args, expected_error) = match invalid_wasmless_transfer { + InvalidWasmlessTransfer::TransferToSelfByAddr => { + // same source and target purse is invalid + ( + ACCOUNT_1_ADDR, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::Revert(ApiError::InvalidPurse)), + ) + } + InvalidWasmlessTransfer::TransferToSelfByKey => { + // same source and target purse is invalid + ( + ACCOUNT_1_ADDR, + runtime_args! { ARG_TARGET => Key::Account(ACCOUNT_1_ADDR), ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::Revert(ApiError::InvalidPurse)), + ) + } + InvalidWasmlessTransfer::TransferToSelfByURef => { + let account_1_purse = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should get account 1") + .main_purse(); + // same source and target purse is invalid + ( + ACCOUNT_1_ADDR, + runtime_args! { ARG_TARGET => account_1_purse, ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::Revert(ApiError::InvalidPurse)), + ) + } + InvalidWasmlessTransfer::OtherSourceAccountByAddr => { + // passes another account's addr as source + ( + ACCOUNT_1_ADDR, + runtime_args! { ARG_SOURCE => ACCOUNT_2_ADDR, ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::Revert(ApiError::InvalidArgument)), + ) + } + InvalidWasmlessTransfer::OtherSourceAccountByKey => { + // passes another account's Key::Account as source + ( + ACCOUNT_1_ADDR, + runtime_args! { ARG_SOURCE => Key::Account(ACCOUNT_2_ADDR), ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::Revert(ApiError::InvalidArgument)), + ) + } + InvalidWasmlessTransfer::OtherSourceAccountByURef => { + let account_2_purse = builder + .get_account(ACCOUNT_2_ADDR) + .expect("should get account 1") + .main_purse(); + // passes another account's purse as source + ( + ACCOUNT_1_ADDR, + runtime_args! { ARG_SOURCE => account_2_purse, ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::ForgedReference(account_2_purse)), + ) + } + InvalidWasmlessTransfer::MissingTarget => { + // does not pass target + ( + ACCOUNT_1_ADDR, + runtime_args! { ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::Revert(ApiError::MissingArgument)), + ) + } + InvalidWasmlessTransfer::MissingAmount => { + // does not pass amount + ( + ACCOUNT_1_ADDR, + runtime_args! { ARG_TARGET => ACCOUNT_2_ADDR }, + CoreError::Exec(ExecError::Revert(ApiError::MissingArgument)), + ) + } + InvalidWasmlessTransfer::SourceURefNotPurse => { + let not_purse_uref = + get_default_account_named_uref(&mut builder, TRANSFER_RESULT_NAMED_KEY); + // passes an invalid uref as source (an existing uref that is not a purse uref) + ( + DEFAULT_ACCOUNT_ADDR, + runtime_args! { ARG_SOURCE => not_purse_uref, ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::Revert(ApiError::InvalidPurse)), + ) + } + InvalidWasmlessTransfer::TargetURefNotPurse => { + let not_purse_uref = + get_default_account_named_uref(&mut builder, TRANSFER_RESULT_NAMED_KEY); + // passes an invalid uref as target (an existing uref that is not a purse uref) + ( + DEFAULT_ACCOUNT_ADDR, + runtime_args! { ARG_TARGET => not_purse_uref, ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::Revert(ApiError::InvalidPurse)), + ) + } + InvalidWasmlessTransfer::SourceURefNonexistent => { + let nonexistent_purse = URef::new([255; 32], AccessRights::READ_ADD_WRITE); + // passes a nonexistent uref as source; considered to be a forged reference as when + // a caller passes a uref as source they are claiming it is a purse and that they have + // write access to it / are allowed to take funds from it. + ( + ACCOUNT_1_ADDR, + runtime_args! { ARG_SOURCE => nonexistent_purse, ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::ForgedReference(nonexistent_purse)), + ) + } + InvalidWasmlessTransfer::TargetURefNonexistent => { + let nonexistent_purse = URef::new([255; 32], AccessRights::READ_ADD_WRITE); + // passes a nonexistent uref as target + ( + ACCOUNT_1_ADDR, + runtime_args! { ARG_TARGET => nonexistent_purse, ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::Revert(ApiError::InvalidPurse)), + ) + } + InvalidWasmlessTransfer::OtherPurseToSelfPurse => { + let account_1_purse = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should get account 1") + .main_purse(); + let account_2_purse = builder + .get_account(ACCOUNT_2_ADDR) + .expect("should get account 1") + .main_purse(); + + // attempts to take from an unowned purse + ( + ACCOUNT_1_ADDR, + runtime_args! { ARG_SOURCE => account_2_purse, ARG_TARGET => account_1_purse, ARG_AMOUNT => transfer_amount }, + CoreError::Exec(ExecError::ForgedReference(account_2_purse)), + ) + } + }; + + let no_wasm_transfer_request = { + let deploy_item = DeployItemBuilder::new() + .with_address(addr) + .with_empty_payment_bytes(runtime_args! {}) + .with_transfer_args(runtime_args) + .with_authorization_keys(&[addr]) + .build(); + ExecuteRequestBuilder::from_deploy_item(deploy_item).build() + }; + + builder.exec(no_wasm_transfer_request); + + let result = builder + .get_exec_responses() + .last() + .expect("Expected to be called after run()") + .get(0) + .expect("Unable to get first deploy result"); + + assert!(result.is_failure(), "was expected to fail"); + + let error = result.as_error().expect("should have error"); + + assert_eq!( + format!("{}", &expected_error), + format!("{}", error), + "expected_error: {} actual error: {}", + expected_error, + error + ); +} + +#[ignore] +#[test] +fn transfer_wasmless_should_create_target_if_it_doesnt_exist() { + let create_account_2: bool = false; + let mut builder = init_wasmless_transform_builder(create_account_2); + let transfer_amount: U512 = U512::from(1000); + + let account_1_purse = builder + .get_account(ACCOUNT_1_ADDR) + .expect("should get account 1") + .main_purse(); + + assert_eq!( + builder.get_account(ACCOUNT_2_ADDR), + None, + "account 2 should not exist" + ); + + let account_1_starting_balance = builder.get_purse_balance(account_1_purse); + + let runtime_args = + runtime_args! { ARG_TARGET => ACCOUNT_2_ADDR, ARG_AMOUNT => transfer_amount }; + + let no_wasm_transfer_request = { + let deploy_item = DeployItemBuilder::new() + .with_address(ACCOUNT_1_ADDR) + .with_empty_payment_bytes(runtime_args! {}) + .with_transfer_args(runtime_args) + .with_authorization_keys(&[ACCOUNT_1_ADDR]) + .build(); + ExecuteRequestBuilder::from_deploy_item(deploy_item).build() + }; + + builder + .exec(no_wasm_transfer_request) + .expect_success() + .commit(); + + let account_2 = builder + .get_account(ACCOUNT_2_ADDR) + .expect("account 2 should exist"); + + let account_2_starting_balance = builder.get_purse_balance(account_2.main_purse()); + + assert_eq!( + account_1_starting_balance - transfer_amount, + builder.get_purse_balance(account_1_purse), + "account 1 ending balance incorrect" + ); + assert_eq!( + account_2_starting_balance, transfer_amount, + "account 2 ending balance incorrect" + ); +} + +fn get_default_account_named_uref(builder: &mut InMemoryWasmTestBuilder, name: &str) -> URef { + let default_account = builder + .get_account(DEFAULT_ACCOUNT_ADDR) + .expect("default account should exist"); + default_account + .named_keys() + .get(name) + .expect("default account should have named key") + .as_uref() + .expect("should be a uref") + .to_owned() +} + +fn init_wasmless_transform_builder(create_account_2: bool) -> InMemoryWasmTestBuilder { + let mut builder = InMemoryWasmTestBuilder::default(); + let create_account_1_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_1_ADDR, ARG_AMOUNT => *DEFAULT_PAYMENT }, + ) + .build(); + + builder + .run_genesis(&DEFAULT_RUN_GENESIS_REQUEST) + .exec(create_account_1_request) + .expect_success() + .commit(); + + if !create_account_2 { + return builder; + } + + let create_account_2_request = ExecuteRequestBuilder::standard( + DEFAULT_ACCOUNT_ADDR, + CONTRACT_TRANSFER_PURSE_TO_ACCOUNT, + runtime_args! { ARG_TARGET => ACCOUNT_2_ADDR, ARG_AMOUNT => *DEFAULT_PAYMENT }, + ) + .build(); + + builder + .exec(create_account_2_request) + .commit() + .expect_success() + .to_owned() +} diff --git a/node/Cargo.toml b/node/Cargo.toml new file mode 100644 index 0000000000..451b4fc0e9 --- /dev/null +++ b/node/Cargo.toml @@ -0,0 +1,115 @@ +[package] +name = "casperlabs-node" +version = "0.1.0" +authors = ["Marc Brinkmann ", "Fraser Hutchison "] +edition = "2018" +description = "The CasperLabs blockchain node" +documentation = "https://docs.rs/casperlabs-node" +readme = "README.md" +homepage = "https://casperlabs.io" +repository = "https://github.com/CasperLabs/casperlabs-node" +license-file = "LICENSE" +publish = false # Prevent accidental `cargo publish` for now. +default-run = "casperlabs-node" + +[dependencies] +ansi_term = "0.12.1" +anyhow = "1.0.28" +base16 = "0.2.1" +bincode = "1.2.1" +blake2 = { version = "0.8.1", default-features = false } +bytes = "0.5.4" +derive_more = "0.99.7" +directories = "2.0.2" +ed25519-dalek = { version = "1.0.0-pre.3", default-features = false, features = ["rand", "serde", "u64_backend"] } +either = "1.5.3" +enum-iterator = "0.6.0" +failure = "0.1.6" +futures = "0.3.5" +getrandom = "0.1.14" +hex = "0.4.2" +hex_fmt = "0.3.0" +http = "0.2.1" +itertools = "0.8.2" +lazy_static = "1.4.0" +linked-hash-map = "0.5.2" +lmdb = "0.8.0" +maplit = "1.0.2" +num-derive = "0.3.0" +num-traits = "0.2.10" +openssl = "0.10.29" +parity-wasm = "0.41.0" +pwasm-utils = "0.12.0" +rand = "0.7.3" +rand_chacha = "0.2.2" +reqwest = "0.10.6" +rmp-serde = "0.14.3" +serde = { version = "1.0.110", features = ["derive"] } +serde_json = "1.0.55" +serde-big-array = "0.3.0" +smallvec = "1.4.0" +structopt = "0.3.14" +tempfile = "3.1.0" +thiserror = "1.0.18" +tokio = { version = "0.2.20", features = ["macros", "rt-threaded", "sync", "tcp", "time", "blocking"] } +tokio-openssl = "0.4.0" +tokio-serde = { version = "0.6.1", features = ["messagepack"] } +tokio-util = { version = "0.3.1", features = ["codec"] } +toml = "0.5.6" +tracing = "0.1.14" +tracing-subscriber = "0.2.5" +warp = "0.2.3" +wasmi = "0.6.2" +types = { path = "../types", package = "casperlabs-types", features = ["std", "gens"] } + +### + +chrono = "0.4.10" +hostname = "0.3.0" +libc = "0.2.66" +log = { version = "0.4.8", features = ["std", "serde", "kv_unstable"] } +num = { version = "0.2.0", default-features = false } +proptest = { version = "0.9.4", optional = true } +uuid = { version = "0.8.1", features = ["serde", "v4"] } +wabt = "0.9.2" + +### +parking_lot = "0.10.0" + +[features] +vendored-openssl = ['openssl/vendored'] +test-support = [] +no-unstable-features = [ + "types/no-unstable-features", +] +gens = ["proptest"] + +[[bin]] +name = "casperlabs-node" +path = "src/apps/node/main.rs" +bench = false +doctest = false +test = false + +[[bin]] +name = "casperlabs-client" +path = "src/apps/client/main.rs" +bench = false +doctest = false +test = false + +[package.metadata.deb] +features = ["vendored-openssl"] +assets = [ + ["./target/release/casperlabs-node","/usr/bin/casperlabs-node", "755"], + ["./target/release/casperlabs-client","/usr/bin/casperlabs-client", "755"] +] + +[dev-dependencies] +assert_matches = "1.3.0" +lazy_static = "1" +pnet = "0.26.0" +proptest = "0.9.4" +rand_core = "0.5.1" +rand_xorshift = { version = "~0.2.0" } +fake_instant = "0.4.0" \ No newline at end of file diff --git a/node/benches/trie_bench.rs b/node/benches/trie_bench.rs new file mode 100644 index 0000000000..b9a83c2b3f --- /dev/null +++ b/node/benches/trie_bench.rs @@ -0,0 +1,71 @@ +#![feature(test)] + +extern crate test; + +use test::{black_box, Bencher}; + +use casperlabs_node::contract_shared::{newtypes::Blake2bHash, stored_value::StoredValue}; +use casperlabs_node::contract_storage::trie::{Pointer, PointerBlock, Trie}; +use types::{ + account::AccountHash, + bytesrepr::{FromBytes, ToBytes}, + CLValue, Key, +}; + +#[bench] +fn serialize_trie_leaf(b: &mut Bencher) { + let leaf = Trie::Leaf { + key: Key::Account(AccountHash::new([0; 32])), + value: StoredValue::CLValue(CLValue::from_t(42_i32).unwrap()), + }; + b.iter(|| ToBytes::to_bytes(black_box(&leaf))); +} + +#[bench] +fn deserialize_trie_leaf(b: &mut Bencher) { + let leaf = Trie::Leaf { + key: Key::Account(AccountHash::new([0; 32])), + value: StoredValue::CLValue(CLValue::from_t(42_i32).unwrap()), + }; + let leaf_bytes = leaf.to_bytes().unwrap(); + b.iter(|| Trie::::from_bytes(black_box(&leaf_bytes))); +} + +#[bench] +fn serialize_trie_node(b: &mut Bencher) { + let node = Trie::::Node { + pointer_block: Box::new(PointerBlock::default()), + }; + b.iter(|| ToBytes::to_bytes(black_box(&node))); +} + +#[bench] +fn deserialize_trie_node(b: &mut Bencher) { + let node = Trie::::Node { + pointer_block: Box::new(PointerBlock::default()), + }; + let node_bytes = node.to_bytes().unwrap(); + + b.iter(|| Trie::::from_bytes(black_box(&node_bytes))); +} + +#[bench] +fn serialize_trie_node_pointer(b: &mut Bencher) { + let node = Trie::::Extension { + affix: (0..255).collect(), + pointer: Pointer::NodePointer(Blake2bHash::new(&[0; 32])), + }; + + b.iter(|| ToBytes::to_bytes(black_box(&node))); +} + +#[bench] +fn deserialize_trie_node_pointer(b: &mut Bencher) { + let node = Trie::::Extension { + affix: (0..255).collect(), + pointer: Pointer::NodePointer(Blake2bHash::new(&[0; 32])), + }; + let node_bytes = node.to_bytes().unwrap(); + + b.iter(|| Trie::::from_bytes(black_box(&node_bytes))); +} diff --git a/src/apps/client/main.rs b/node/src/apps/client/main.rs similarity index 100% rename from src/apps/client/main.rs rename to node/src/apps/client/main.rs diff --git a/src/apps/node/cli.rs b/node/src/apps/node/cli.rs similarity index 100% rename from src/apps/node/cli.rs rename to node/src/apps/node/cli.rs diff --git a/src/apps/node/config.rs b/node/src/apps/node/config.rs similarity index 100% rename from src/apps/node/config.rs rename to node/src/apps/node/config.rs diff --git a/src/logging.rs b/node/src/apps/node/logging.rs similarity index 100% rename from src/logging.rs rename to node/src/apps/node/logging.rs diff --git a/src/apps/node/main.rs b/node/src/apps/node/main.rs similarity index 100% rename from src/apps/node/main.rs rename to node/src/apps/node/main.rs diff --git a/src/components.rs b/node/src/components.rs similarity index 98% rename from src/components.rs rename to node/src/components.rs index 30322de151..a013c8708a 100644 --- a/src/components.rs +++ b/node/src/components.rs @@ -4,6 +4,7 @@ //! Each component has a unified interface, expressed by the `Component` trait. pub(crate) mod api_server; pub(crate) mod consensus; +pub mod contract_runtime; pub(crate) mod deploy_gossiper; // The `in_memory_network` is public for use in doctests. pub mod in_memory_network; diff --git a/src/components/api_server.rs b/node/src/components/api_server.rs similarity index 100% rename from src/components/api_server.rs rename to node/src/components/api_server.rs diff --git a/src/components/api_server/config.rs b/node/src/components/api_server/config.rs similarity index 100% rename from src/components/api_server/config.rs rename to node/src/components/api_server/config.rs diff --git a/src/components/api_server/event.rs b/node/src/components/api_server/event.rs similarity index 100% rename from src/components/api_server/event.rs rename to node/src/components/api_server/event.rs diff --git a/src/components/consensus.rs b/node/src/components/consensus.rs similarity index 100% rename from src/components/consensus.rs rename to node/src/components/consensus.rs diff --git a/src/components/consensus/consensus_protocol.rs b/node/src/components/consensus/consensus_protocol.rs similarity index 100% rename from src/components/consensus/consensus_protocol.rs rename to node/src/components/consensus/consensus_protocol.rs diff --git a/src/components/consensus/consensus_protocol/protocol_state.rs b/node/src/components/consensus/consensus_protocol/protocol_state.rs similarity index 100% rename from src/components/consensus/consensus_protocol/protocol_state.rs rename to node/src/components/consensus/consensus_protocol/protocol_state.rs diff --git a/src/components/consensus/consensus_protocol/synchronizer.rs b/node/src/components/consensus/consensus_protocol/synchronizer.rs similarity index 100% rename from src/components/consensus/consensus_protocol/synchronizer.rs rename to node/src/components/consensus/consensus_protocol/synchronizer.rs diff --git a/src/components/consensus/deploy_buffer.rs b/node/src/components/consensus/deploy_buffer.rs similarity index 100% rename from src/components/consensus/deploy_buffer.rs rename to node/src/components/consensus/deploy_buffer.rs diff --git a/src/components/consensus/era_supervisor.rs b/node/src/components/consensus/era_supervisor.rs similarity index 100% rename from src/components/consensus/era_supervisor.rs rename to node/src/components/consensus/era_supervisor.rs diff --git a/src/components/consensus/highway_core.rs b/node/src/components/consensus/highway_core.rs similarity index 100% rename from src/components/consensus/highway_core.rs rename to node/src/components/consensus/highway_core.rs diff --git a/src/components/consensus/highway_core/active_validator.rs b/node/src/components/consensus/highway_core/active_validator.rs similarity index 100% rename from src/components/consensus/highway_core/active_validator.rs rename to node/src/components/consensus/highway_core/active_validator.rs diff --git a/src/components/consensus/highway_core/block.rs b/node/src/components/consensus/highway_core/block.rs similarity index 100% rename from src/components/consensus/highway_core/block.rs rename to node/src/components/consensus/highway_core/block.rs diff --git a/src/components/consensus/highway_core/evidence.rs b/node/src/components/consensus/highway_core/evidence.rs similarity index 100% rename from src/components/consensus/highway_core/evidence.rs rename to node/src/components/consensus/highway_core/evidence.rs diff --git a/src/components/consensus/highway_core/finality_detector.rs b/node/src/components/consensus/highway_core/finality_detector.rs similarity index 100% rename from src/components/consensus/highway_core/finality_detector.rs rename to node/src/components/consensus/highway_core/finality_detector.rs diff --git a/src/components/consensus/highway_core/highway.rs b/node/src/components/consensus/highway_core/highway.rs similarity index 100% rename from src/components/consensus/highway_core/highway.rs rename to node/src/components/consensus/highway_core/highway.rs diff --git a/src/components/consensus/highway_core/state.rs b/node/src/components/consensus/highway_core/state.rs similarity index 100% rename from src/components/consensus/highway_core/state.rs rename to node/src/components/consensus/highway_core/state.rs diff --git a/src/components/consensus/highway_core/tallies.rs b/node/src/components/consensus/highway_core/tallies.rs similarity index 100% rename from src/components/consensus/highway_core/tallies.rs rename to node/src/components/consensus/highway_core/tallies.rs diff --git a/src/components/consensus/highway_core/test_macros.rs b/node/src/components/consensus/highway_core/test_macros.rs similarity index 100% rename from src/components/consensus/highway_core/test_macros.rs rename to node/src/components/consensus/highway_core/test_macros.rs diff --git a/src/components/consensus/highway_core/validators.rs b/node/src/components/consensus/highway_core/validators.rs similarity index 100% rename from src/components/consensus/highway_core/validators.rs rename to node/src/components/consensus/highway_core/validators.rs diff --git a/src/components/consensus/highway_core/vertex.rs b/node/src/components/consensus/highway_core/vertex.rs similarity index 100% rename from src/components/consensus/highway_core/vertex.rs rename to node/src/components/consensus/highway_core/vertex.rs diff --git a/src/components/consensus/highway_core/vote.rs b/node/src/components/consensus/highway_core/vote.rs similarity index 100% rename from src/components/consensus/highway_core/vote.rs rename to node/src/components/consensus/highway_core/vote.rs diff --git a/src/components/consensus/highway_testing.rs b/node/src/components/consensus/highway_testing.rs similarity index 100% rename from src/components/consensus/highway_testing.rs rename to node/src/components/consensus/highway_testing.rs diff --git a/src/components/consensus/protocols.rs b/node/src/components/consensus/protocols.rs similarity index 100% rename from src/components/consensus/protocols.rs rename to node/src/components/consensus/protocols.rs diff --git a/src/components/consensus/protocols/highway.rs b/node/src/components/consensus/protocols/highway.rs similarity index 100% rename from src/components/consensus/protocols/highway.rs rename to node/src/components/consensus/protocols/highway.rs diff --git a/src/components/consensus/traits.rs b/node/src/components/consensus/traits.rs similarity index 100% rename from src/components/consensus/traits.rs rename to node/src/components/consensus/traits.rs diff --git a/node/src/components/contract_runtime.rs b/node/src/components/contract_runtime.rs new file mode 100644 index 0000000000..334906eaf7 --- /dev/null +++ b/node/src/components/contract_runtime.rs @@ -0,0 +1,126 @@ +//! Contract Runtime component. +use std::{ + fmt::{Debug, Display}, + path::PathBuf, + sync::Arc, +}; + +use lmdb::DatabaseFlags; +use rand::Rng; +use serde::{Deserialize, Serialize}; + +use crate::contract_core::engine_state::{EngineConfig, EngineState}; +use crate::contract_shared::page_size; +use crate::contract_storage::protocol_data_store::lmdb::LmdbProtocolDataStore; +use crate::contract_storage::{ + global_state::lmdb::LmdbGlobalState, transaction_source::lmdb::LmdbEnvironment, + trie_store::lmdb::LmdbTrieStore, +}; + +use crate::components::Component; +use crate::effect::{Effect, EffectBuilder, Multiple}; + +/// The contract runtime components. +pub(crate) struct ContractRuntime { + #[allow(dead_code)] + engine_state: EngineState, +} + +impl Debug for ContractRuntime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ContractRuntime").finish() + } +} + +/// Contract runtime message used by the pinger. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub(crate) struct Message; + +/// Pinger component event. +#[derive(Debug)] +pub enum Event { + /// Foo + Foo, + /// Bar + Bar, +} + +impl Display for Event { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Event::Foo => write!(f, "foo"), + Event::Bar => write!(f, "bar"), + } + } +} + +impl Component for ContractRuntime +where + REv: From + Send, +{ + type Event = Event; + + fn handle_event( + &mut self, + _effect_builder: EffectBuilder, + _rng: &mut R, + event: Self::Event, + ) -> Multiple> { + match event { + Event::Foo => todo!("foo"), + Event::Bar => todo!("bar"), + } + } +} + +/// Builds and returns engine global state +fn get_engine_state( + data_dir: PathBuf, + map_size: usize, + engine_config: EngineConfig, +) -> EngineState { + let environment = { + let ret = LmdbEnvironment::new(&data_dir, map_size).expect("should have lmdb environment"); + Arc::new(ret) + }; + + let trie_store = { + let ret = LmdbTrieStore::new(&environment, None, DatabaseFlags::empty()) + .expect("should have trie store"); + Arc::new(ret) + }; + + let protocol_data_store = { + let ret = LmdbProtocolDataStore::new(&environment, None, DatabaseFlags::empty()) + .expect("should have protocol data store"); + Arc::new(ret) + }; + + let global_state = LmdbGlobalState::empty(environment, trie_store, protocol_data_store) + .expect("should have global state"); + + EngineState::new(global_state, engine_config) +} + +impl ContractRuntime { + /// Create and initialize a new pinger. + pub(crate) fn new + Send>( + _effect_builder: EffectBuilder, + ) -> (Self, Multiple>) { + let engine_config = EngineConfig::new() + .with_use_system_contracts(false) + .with_enable_bonding(false); + + let engine_state = get_engine_state( + PathBuf::from("/tmp"), + page_size::get_page_size().expect("should get page size"), + engine_config, + ); + + let contract_runtime = ContractRuntime { engine_state }; + + let init = Multiple::new(); + + (contract_runtime, init) + } +} diff --git a/src/components/deploy_gossiper.rs b/node/src/components/deploy_gossiper.rs similarity index 100% rename from src/components/deploy_gossiper.rs rename to node/src/components/deploy_gossiper.rs diff --git a/src/components/in_memory_network.rs b/node/src/components/in_memory_network.rs similarity index 100% rename from src/components/in_memory_network.rs rename to node/src/components/in_memory_network.rs diff --git a/src/components/pinger.rs b/node/src/components/pinger.rs similarity index 100% rename from src/components/pinger.rs rename to node/src/components/pinger.rs diff --git a/src/components/small_network.rs b/node/src/components/small_network.rs similarity index 100% rename from src/components/small_network.rs rename to node/src/components/small_network.rs diff --git a/src/components/small_network/config.rs b/node/src/components/small_network/config.rs similarity index 100% rename from src/components/small_network/config.rs rename to node/src/components/small_network/config.rs diff --git a/src/components/small_network/endpoint.rs b/node/src/components/small_network/endpoint.rs similarity index 100% rename from src/components/small_network/endpoint.rs rename to node/src/components/small_network/endpoint.rs diff --git a/src/components/small_network/error.rs b/node/src/components/small_network/error.rs similarity index 100% rename from src/components/small_network/error.rs rename to node/src/components/small_network/error.rs diff --git a/src/components/small_network/event.rs b/node/src/components/small_network/event.rs similarity index 100% rename from src/components/small_network/event.rs rename to node/src/components/small_network/event.rs diff --git a/src/components/small_network/message.rs b/node/src/components/small_network/message.rs similarity index 100% rename from src/components/small_network/message.rs rename to node/src/components/small_network/message.rs diff --git a/src/components/small_network/test.rs b/node/src/components/small_network/test.rs similarity index 100% rename from src/components/small_network/test.rs rename to node/src/components/small_network/test.rs diff --git a/src/components/storage.rs b/node/src/components/storage.rs similarity index 100% rename from src/components/storage.rs rename to node/src/components/storage.rs diff --git a/src/components/storage/config.rs b/node/src/components/storage/config.rs similarity index 88% rename from src/components/storage/config.rs rename to node/src/components/storage/config.rs index 929fa0d6b5..01c9378ab6 100644 --- a/src/components/storage/config.rs +++ b/node/src/components/storage/config.rs @@ -1,7 +1,7 @@ -use std::{io, path::PathBuf}; +use std::path::PathBuf; +use crate::contract_shared::page_size; use directories::ProjectDirs; -use libc::{self, _SC_PAGESIZE}; use serde::{de::Deserializer, Deserialize, Serialize}; use tempfile::TempDir; use tracing::warn; @@ -80,26 +80,13 @@ impl Default for Config { } } -/// Returns OS page size -fn get_page_size() -> Result { - // https://www.gnu.org/software/libc/manual/html_node/Sysconf.html - let value = unsafe { libc::sysconf(_SC_PAGESIZE) }; - - if value < 0 { - warn!("unable to get system page size"); - return Err(io::Error::last_os_error()); - } - - Ok(value as usize) -} - /// Deserializes a `usize` but warns if it is not a multiple of the OS page size. fn deserialize_page_size_multiple<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let value = usize::deserialize(deserializer)?; - let page_size = get_page_size().unwrap_or(1); + let page_size = page_size::get_page_size().unwrap_or(1); if value % page_size != 0 { warn!( "maximum size {} is not multiple of system page size {}", diff --git a/src/components/storage/error.rs b/node/src/components/storage/error.rs similarity index 100% rename from src/components/storage/error.rs rename to node/src/components/storage/error.rs diff --git a/src/components/storage/in_mem_store.rs b/node/src/components/storage/in_mem_store.rs similarity index 100% rename from src/components/storage/in_mem_store.rs rename to node/src/components/storage/in_mem_store.rs diff --git a/src/components/storage/lmdb_store.rs b/node/src/components/storage/lmdb_store.rs similarity index 100% rename from src/components/storage/lmdb_store.rs rename to node/src/components/storage/lmdb_store.rs diff --git a/src/components/storage/store.rs b/node/src/components/storage/store.rs similarity index 100% rename from src/components/storage/store.rs rename to node/src/components/storage/store.rs diff --git a/node/src/contract_core.rs b/node/src/contract_core.rs new file mode 100644 index 0000000000..a158743dcb --- /dev/null +++ b/node/src/contract_core.rs @@ -0,0 +1,15 @@ +#![allow(missing_docs)] + +pub mod engine_state; +pub mod execution; +pub mod resolvers; +pub mod runtime; +pub mod runtime_context; +pub(crate) mod tracking_copy; + +pub const ADDRESS_LENGTH: usize = 32; +pub const DEPLOY_HASH_LENGTH: usize = 32; + +pub type Address = [u8; ADDRESS_LENGTH]; + +pub type DeployHash = [u8; DEPLOY_HASH_LENGTH]; diff --git a/node/src/contract_core/engine_state/deploy_item.rs b/node/src/contract_core/engine_state/deploy_item.rs new file mode 100644 index 0000000000..32a0ccc5cb --- /dev/null +++ b/node/src/contract_core/engine_state/deploy_item.rs @@ -0,0 +1,41 @@ +use std::collections::BTreeSet; + +use types::account::AccountHash; + +use crate::contract_core::{ + engine_state::executable_deploy_item::ExecutableDeployItem, DeployHash, +}; + +type GasPrice = u64; + +/// Represents a deploy to be executed. Corresponds to the similarly-named ipc protobuf message. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct DeployItem { + pub address: AccountHash, + pub session: ExecutableDeployItem, + pub payment: ExecutableDeployItem, + pub gas_price: GasPrice, + pub authorization_keys: BTreeSet, + pub deploy_hash: DeployHash, +} + +impl DeployItem { + /// Creates a [`DeployItem`]. + pub fn new( + address: AccountHash, + session: ExecutableDeployItem, + payment: ExecutableDeployItem, + gas_price: GasPrice, + authorization_keys: BTreeSet, + deploy_hash: DeployHash, + ) -> Self { + DeployItem { + address, + session, + payment, + gas_price, + authorization_keys, + deploy_hash, + } + } +} diff --git a/node/src/contract_core/engine_state/engine_config.rs b/node/src/contract_core/engine_state/engine_config.rs new file mode 100644 index 0000000000..a0885c1f3e --- /dev/null +++ b/node/src/contract_core/engine_state/engine_config.rs @@ -0,0 +1,32 @@ +/// The runtime configuration of the execution engine +#[derive(Debug, Copy, Clone, Default)] +pub struct EngineConfig { + // feature flags go here + use_system_contracts: bool, + enable_bonding: bool, +} + +impl EngineConfig { + /// Creates a new engine configuration with default parameters. + pub fn new() -> EngineConfig { + Default::default() + } + + pub fn use_system_contracts(self) -> bool { + self.use_system_contracts + } + + pub fn with_use_system_contracts(mut self, use_system_contracts: bool) -> EngineConfig { + self.use_system_contracts = use_system_contracts; + self + } + + pub fn enable_bonding(self) -> bool { + self.enable_bonding + } + + pub fn with_enable_bonding(mut self, enable_bonding: bool) -> EngineConfig { + self.enable_bonding = enable_bonding; + self + } +} diff --git a/node/src/contract_core/engine_state/error.rs b/node/src/contract_core/engine_state/error.rs new file mode 100644 index 0000000000..382cf86e9e --- /dev/null +++ b/node/src/contract_core/engine_state/error.rs @@ -0,0 +1,106 @@ +use failure::Fail; + +use crate::contract_shared::newtypes::Blake2bHash; +use crate::contract_shared::wasm_prep; +use types::ProtocolVersion; +use types::{bytesrepr, system_contract_errors::mint}; + +use crate::contract_core::execution; +use crate::contract_storage; + +#[derive(Fail, Debug)] +pub enum Error { + #[fail(display = "Invalid hash length: expected {}, actual {}", _0, _1)] + InvalidHashLength { expected: usize, actual: usize }, + #[fail( + display = "Invalid account hash length: expected {}, actual {}", + _0, _1 + )] + InvalidAccountHashLength { expected: usize, actual: usize }, + #[fail(display = "Invalid protocol version: {}", _0)] + InvalidProtocolVersion(ProtocolVersion), + #[fail(display = "Invalid upgrade config")] + InvalidUpgradeConfig, + #[fail(display = "Wasm preprocessing error: {}", _0)] + WasmPreprocessing(wasm_prep::PreprocessingError), + #[fail(display = "Wasm serialization error: {:?}", _0)] + WasmSerialization(parity_wasm::SerializationError), + #[fail(display = "{}", _0)] + Exec(execution::Error), + #[fail(display = "Storage error: {}", _0)] + Storage(contract_storage::error::Error), + #[fail(display = "Authorization failure: not authorized.")] + Authorization, + #[fail(display = "Insufficient payment")] + InsufficientPayment, + #[fail(display = "Deploy error")] + Deploy, + #[fail(display = "Payment finalization error")] + Finalization, + #[fail(display = "Missing system contract association: {}", _0)] + MissingSystemContract(String), + #[fail(display = "Serialization error: {}", _0)] + Serialization(bytesrepr::Error), + #[fail(display = "Mint error: {}", _0)] + Mint(mint::Error), + #[fail(display = "Unsupported key type: {}", _0)] + InvalidKeyVariant(String), + #[fail(display = "Invalid upgrade result value")] + InvalidUpgradeResult, + #[fail(display = "Unsupported deploy item variant: {}", _0)] + InvalidDeployItemVariant(String), +} + +impl From for Error { + fn from(error: wasm_prep::PreprocessingError) -> Self { + Error::WasmPreprocessing(error) + } +} + +impl From for Error { + fn from(error: parity_wasm::SerializationError) -> Self { + Error::WasmSerialization(error) + } +} + +impl From for Error { + fn from(error: execution::Error) -> Self { + match error { + execution::Error::WasmPreprocessing(preprocessing_error) => { + Error::WasmPreprocessing(preprocessing_error) + } + _ => Error::Exec(error), + } + } +} + +impl From for Error { + fn from(error: contract_storage::error::Error) -> Self { + Error::Storage(error) + } +} + +impl From for Error { + fn from(error: bytesrepr::Error) -> Self { + Error::Serialization(error) + } +} + +impl From for Error { + fn from(error: mint::Error) -> Self { + Error::Mint(error) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct RootNotFound(Blake2bHash); + +impl RootNotFound { + pub fn new(hash: Blake2bHash) -> Self { + RootNotFound(hash) + } + + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } +} diff --git a/node/src/contract_core/engine_state/executable_deploy_item.rs b/node/src/contract_core/engine_state/executable_deploy_item.rs new file mode 100644 index 0000000000..ea8b3e0770 --- /dev/null +++ b/node/src/contract_core/engine_state/executable_deploy_item.rs @@ -0,0 +1,92 @@ +use super::error; +use crate::contract_core::execution; +use crate::contract_shared::account::Account; +use types::{ + bytesrepr, + contracts::{ContractVersion, DEFAULT_ENTRY_POINT_NAME}, + ContractHash, ContractPackageHash, Key, RuntimeArgs, +}; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum ExecutableDeployItem { + ModuleBytes { + module_bytes: Vec, + // assumes implicit `call` noarg entrypoint + args: Vec, + }, + StoredContractByHash { + hash: ContractHash, + entry_point: String, + args: Vec, + }, + StoredContractByName { + name: String, + entry_point: String, + args: Vec, + }, + StoredVersionedContractByName { + name: String, + version: Option, // defaults to highest enabled version + entry_point: String, + args: Vec, + }, + StoredVersionedContractByHash { + hash: ContractPackageHash, + version: Option, // defaults to highest enabled version + entry_point: String, + args: Vec, + }, + Transfer { + args: Vec, + }, +} + +impl ExecutableDeployItem { + pub(crate) fn to_contract_hash_key( + &self, + account: &Account, + ) -> Result, error::Error> { + match self { + ExecutableDeployItem::StoredContractByHash { hash, .. } + | ExecutableDeployItem::StoredVersionedContractByHash { hash, .. } => { + Ok(Some(Key::from(*hash))) + } + ExecutableDeployItem::StoredContractByName { name, .. } + | ExecutableDeployItem::StoredVersionedContractByName { name, .. } => { + let key = account.named_keys().get(name).cloned().ok_or_else(|| { + error::Error::Exec(execution::Error::NamedKeyNotFound(name.to_string())) + })?; + Ok(Some(key)) + } + ExecutableDeployItem::ModuleBytes { .. } | ExecutableDeployItem::Transfer { .. } => { + Ok(None) + } + } + } + + pub fn into_runtime_args(self) -> Result { + match self { + ExecutableDeployItem::ModuleBytes { args, .. } + | ExecutableDeployItem::StoredContractByHash { args, .. } + | ExecutableDeployItem::StoredContractByName { args, .. } + | ExecutableDeployItem::StoredVersionedContractByHash { args, .. } + | ExecutableDeployItem::StoredVersionedContractByName { args, .. } + | ExecutableDeployItem::Transfer { args } => { + let runtime_args: RuntimeArgs = bytesrepr::deserialize(args)?; + Ok(runtime_args) + } + } + } + + pub fn entry_point_name(&self) -> &str { + match self { + ExecutableDeployItem::ModuleBytes { .. } | ExecutableDeployItem::Transfer { .. } => { + DEFAULT_ENTRY_POINT_NAME + } + ExecutableDeployItem::StoredVersionedContractByName { entry_point, .. } + | ExecutableDeployItem::StoredVersionedContractByHash { entry_point, .. } + | ExecutableDeployItem::StoredContractByHash { entry_point, .. } + | ExecutableDeployItem::StoredContractByName { entry_point, .. } => &entry_point, + } + } +} diff --git a/node/src/contract_core/engine_state/execute_request.rs b/node/src/contract_core/engine_state/execute_request.rs new file mode 100644 index 0000000000..3e59829008 --- /dev/null +++ b/node/src/contract_core/engine_state/execute_request.rs @@ -0,0 +1,45 @@ +use std::mem; + +use crate::contract_shared::newtypes::Blake2bHash; +use types::ProtocolVersion; + +use super::{deploy_item::DeployItem, execution_result::ExecutionResult}; + +#[derive(Debug)] +pub struct ExecuteRequest { + pub parent_state_hash: Blake2bHash, + pub block_time: u64, + pub deploys: Vec>, + pub protocol_version: ProtocolVersion, +} + +impl ExecuteRequest { + pub fn new( + parent_state_hash: Blake2bHash, + block_time: u64, + deploys: Vec>, + protocol_version: ProtocolVersion, + ) -> Self { + Self { + parent_state_hash, + block_time, + deploys, + protocol_version, + } + } + + pub fn take_deploys(&mut self) -> Vec> { + mem::replace(&mut self.deploys, vec![]) + } +} + +impl Default for ExecuteRequest { + fn default() -> Self { + Self { + parent_state_hash: [0u8; 32].into(), + block_time: 0, + deploys: vec![], + protocol_version: Default::default(), + } + } +} diff --git a/node/src/contract_core/engine_state/execution_effect.rs b/node/src/contract_core/engine_state/execution_effect.rs new file mode 100644 index 0000000000..d88ba4834c --- /dev/null +++ b/node/src/contract_core/engine_state/execution_effect.rs @@ -0,0 +1,16 @@ +use crate::contract_shared::{additive_map::AdditiveMap, transform::Transform}; +use types::Key; + +use super::op::Op; + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ExecutionEffect { + pub ops: AdditiveMap, + pub transforms: AdditiveMap, +} + +impl ExecutionEffect { + pub fn new(ops: AdditiveMap, transforms: AdditiveMap) -> Self { + ExecutionEffect { ops, transforms } + } +} diff --git a/node/src/contract_core/engine_state/execution_result.rs b/node/src/contract_core/engine_state/execution_result.rs new file mode 100644 index 0000000000..3eed1d2ec4 --- /dev/null +++ b/node/src/contract_core/engine_state/execution_result.rs @@ -0,0 +1,373 @@ +use super::{error, execution_effect::ExecutionEffect, op::Op, CONV_RATE}; +use crate::contract_shared::{ + additive_map::AdditiveMap, gas::Gas, motes::Motes, newtypes::CorrelationId, + stored_value::StoredValue, transform::Transform, +}; +use crate::contract_storage::global_state::StateReader; +use types::{bytesrepr::FromBytes, CLTyped, CLValue, Key}; + +fn make_payment_error_effects( + max_payment_cost: Motes, + account_main_purse_balance: Motes, + account_main_purse: Key, + rewards_purse: Key, +) -> ExecutionEffect { + let mut ops = AdditiveMap::new(); + let mut transforms = AdditiveMap::new(); + + let new_balance = account_main_purse_balance - max_payment_cost; + // from_t for U512 is assumed to never panic + let new_balance_clvalue = CLValue::from_t(new_balance.value()).unwrap(); + let new_balance_value = StoredValue::CLValue(new_balance_clvalue); + + let account_main_purse_normalize = account_main_purse.normalize(); + let rewards_purse_normalize = rewards_purse.normalize(); + + ops.insert(account_main_purse_normalize, Op::Write); + transforms.insert( + account_main_purse_normalize, + Transform::Write(new_balance_value), + ); + + ops.insert(rewards_purse_normalize, Op::Add); + transforms.insert( + rewards_purse_normalize, + Transform::AddUInt512(max_payment_cost.value()), + ); + + ExecutionEffect::new(ops, transforms) +} + +#[derive(Debug)] +pub enum ExecutionResult { + /// An error condition that happened during execution + Failure { + error: error::Error, + effect: ExecutionEffect, + cost: Gas, + }, + /// Execution was finished successfully + Success { effect: ExecutionEffect, cost: Gas }, +} + +pub enum ForcedTransferResult { + /// Payment code ran out of gas during execution + InsufficientPayment, + /// Payment code execution resulted in an error + PaymentFailure, +} + +impl ExecutionResult { + /// Constructs [ExecutionResult::Failure] that has 0 cost and no effects. + /// This is the case for failures that we can't (or don't want to) charge + /// for, like `PreprocessingError` or `InvalidNonce`. + pub fn precondition_failure(error: error::Error) -> ExecutionResult { + ExecutionResult::Failure { + error, + effect: Default::default(), + cost: Gas::default(), + } + } + + pub fn is_success(&self) -> bool { + match self { + ExecutionResult::Failure { .. } => false, + ExecutionResult::Success { .. } => true, + } + } + + pub fn is_failure(&self) -> bool { + match self { + ExecutionResult::Failure { .. } => true, + ExecutionResult::Success { .. } => false, + } + } + + pub fn has_precondition_failure(&self) -> bool { + match self { + ExecutionResult::Failure { cost, effect, .. } => { + cost.value() == 0.into() && *effect == Default::default() + } + ExecutionResult::Success { .. } => false, + } + } + + pub fn cost(&self) -> Gas { + match self { + ExecutionResult::Failure { cost, .. } => *cost, + ExecutionResult::Success { cost, .. } => *cost, + } + } + + pub fn effect(&self) -> &ExecutionEffect { + match self { + ExecutionResult::Failure { effect, .. } => effect, + ExecutionResult::Success { effect, .. } => effect, + } + } + + pub fn with_cost(self, cost: Gas) -> Self { + match self { + ExecutionResult::Failure { error, effect, .. } => ExecutionResult::Failure { + error, + effect, + cost, + }, + ExecutionResult::Success { effect, .. } => ExecutionResult::Success { effect, cost }, + } + } + + pub fn with_effect(self, effect: ExecutionEffect) -> Self { + match self { + ExecutionResult::Failure { error, cost, .. } => ExecutionResult::Failure { + error, + effect, + cost, + }, + ExecutionResult::Success { cost, .. } => ExecutionResult::Success { effect, cost }, + } + } + + pub fn as_error(&self) -> Option<&error::Error> { + match self { + ExecutionResult::Failure { error, .. } => Some(error), + ExecutionResult::Success { .. } => None, + } + } + + /// Consumes [`ExecutionResult`] instance and optionally returns [`error::Error`] instance for + /// [`ExecutionResult::Failure`] variant. + pub fn take_error(self) -> Option { + match self { + ExecutionResult::Failure { error, .. } => Some(error), + ExecutionResult::Success { .. } => None, + } + } + + pub fn check_forced_transfer( + &self, + payment_purse_balance: Motes, + ) -> Option { + let payment_result_cost = match Motes::from_gas(self.cost(), CONV_RATE) { + Some(cost) => cost, + // Multiplying cost by CONV_RATE overflowed the U512 range + None => return Some(ForcedTransferResult::InsufficientPayment), + }; + // payment_code_spec_3_b_ii: if (balance of PoS pay purse) < (gas spent during + // payment code execution) * conv_rate, no session + let insufficient_balance_to_continue = payment_purse_balance < payment_result_cost; + + match self { + ExecutionResult::Success { .. } if insufficient_balance_to_continue => { + // payment_code_spec_4: insufficient payment + Some(ForcedTransferResult::InsufficientPayment) + } + ExecutionResult::Success { .. } => { + // payment_code_spec_3_b_ii: continue execution + None + } + ExecutionResult::Failure { .. } => { + // payment_code_spec_3_a: report payment error in the deploy response + Some(ForcedTransferResult::PaymentFailure) + } + } + } + + pub fn new_payment_code_error( + error: error::Error, + max_payment_cost: Motes, + account_main_purse_balance: Motes, + account_main_purse: Key, + rewards_purse: Key, + ) -> ExecutionResult { + let effect = make_payment_error_effects( + max_payment_cost, + account_main_purse_balance, + account_main_purse, + rewards_purse, + ); + let cost = Gas::from_motes(max_payment_cost, CONV_RATE).unwrap_or_default(); + ExecutionResult::Failure { + error, + effect, + cost, + } + } + + pub fn take_with_ret(self, ret: T) -> (Option, Self) { + (Some(ret), self) + } + + pub fn take_without_ret(self) -> (Option, Self) { + (None, self) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ExecutionResultBuilderError { + MissingPaymentExecutionResult, + MissingSessionExecutionResult, + MissingFinalizeExecutionResult, +} + +pub struct ExecutionResultBuilder { + payment_execution_result: Option, + session_execution_result: Option, + finalize_execution_result: Option, +} + +impl Default for ExecutionResultBuilder { + fn default() -> Self { + ExecutionResultBuilder { + payment_execution_result: None, + session_execution_result: None, + finalize_execution_result: None, + } + } +} + +impl ExecutionResultBuilder { + pub fn new() -> ExecutionResultBuilder { + ExecutionResultBuilder::default() + } + + pub fn set_payment_execution_result(&mut self, payment_result: ExecutionResult) -> &mut Self { + self.payment_execution_result = Some(payment_result); + self + } + + pub fn set_session_execution_result( + &mut self, + session_execution_result: ExecutionResult, + ) -> &mut ExecutionResultBuilder { + self.session_execution_result = Some(session_execution_result); + self + } + + pub fn set_finalize_execution_result( + &mut self, + finalize_execution_result: ExecutionResult, + ) -> &mut ExecutionResultBuilder { + self.finalize_execution_result = Some(finalize_execution_result); + self + } + + pub fn total_cost(&self) -> Gas { + let payment_cost = self + .payment_execution_result + .as_ref() + .map(ExecutionResult::cost) + .unwrap_or_default(); + let session_cost = self + .session_execution_result + .as_ref() + .map(ExecutionResult::cost) + .unwrap_or_default(); + payment_cost + session_cost + } + + pub fn build>( + self, + reader: &R, + correlation_id: CorrelationId, + ) -> Result { + let cost = self.total_cost(); + let mut ops = AdditiveMap::new(); + let mut transforms = AdditiveMap::new(); + + let mut ret: ExecutionResult = ExecutionResult::Success { + effect: Default::default(), + cost, + }; + + match self.payment_execution_result { + Some(result) => { + if result.is_failure() { + return Ok(result); + } else { + Self::add_effects(&mut ops, &mut transforms, result.effect()); + } + } + None => return Err(ExecutionResultBuilderError::MissingPaymentExecutionResult), + }; + + // session_code_spec_3: only include session exec effects if there is no session + // exec error + match self.session_execution_result { + Some(result) => { + if result.is_failure() { + ret = result.with_cost(cost); + } else { + Self::add_effects(&mut ops, &mut transforms, result.effect()); + } + } + None => return Err(ExecutionResultBuilderError::MissingSessionExecutionResult), + }; + + match self.finalize_execution_result { + Some(result) => { + if result.is_failure() { + // payment_code_spec_5_a: Finalization Error should only ever be raised here + return Ok(ExecutionResult::precondition_failure( + error::Error::Finalization, + )); + } else { + Self::add_effects(&mut ops, &mut transforms, result.effect()); + } + } + None => return Err(ExecutionResultBuilderError::MissingFinalizeExecutionResult), + } + + // Remove redundant writes to allow more opportunity to commute + let reduced_effect = Self::reduce_identity_writes(ops, transforms, reader, correlation_id); + + Ok(ret.with_effect(reduced_effect)) + } + + fn add_effects( + ops: &mut AdditiveMap, + transforms: &mut AdditiveMap, + effect: &ExecutionEffect, + ) { + for (k, op) in effect.ops.iter() { + ops.insert_add(*k, *op); + } + for (k, t) in effect.transforms.iter() { + transforms.insert_add(*k, t.clone()) + } + } + + /// In the case we are writing the same value as was there originally, + /// it is equivalent to having a `Transform::Identity` and `Op::Read`. + /// This function makes that reduction before returning the `ExecutionEffect`. + fn reduce_identity_writes>( + mut ops: AdditiveMap, + mut transforms: AdditiveMap, + reader: &R, + correlation_id: CorrelationId, + ) -> ExecutionEffect { + let kvs: Vec<(Key, StoredValue)> = transforms + .keys() + .filter_map(|k| match transforms.get(k) { + Some(Transform::Write(_)) => reader + .read(correlation_id, k) + .ok() + .and_then(|maybe_v| maybe_v.map(|v| (*k, v))), + _ => None, + }) + .collect(); + + for (k, old_value) in kvs { + if let Some(Transform::Write(new_value)) = transforms.remove(&k) { + if new_value == old_value { + transforms.insert(k, Transform::Identity); + ops.insert(k, Op::Read); + } else { + transforms.insert(k, Transform::Write(new_value)); + } + } + } + + ExecutionEffect::new(ops, transforms) + } +} diff --git a/node/src/contract_core/engine_state/genesis.rs b/node/src/contract_core/engine_state/genesis.rs new file mode 100644 index 0000000000..180ef13819 --- /dev/null +++ b/node/src/contract_core/engine_state/genesis.rs @@ -0,0 +1,285 @@ +use std::{fmt, iter}; + +use num_traits::Zero; +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; + +use crate::contract_shared::wasm_costs::WasmCosts; +use crate::contract_shared::{motes::Motes, newtypes::Blake2bHash, TypeMismatch}; +use crate::contract_storage::global_state::CommitResult; +use types::{account::AccountHash, bytesrepr, Key, ProtocolVersion, U512}; + +use crate::contract_core::engine_state::execution_effect::ExecutionEffect; + +pub const PLACEHOLDER_KEY: Key = Key::Hash([0u8; 32]); +pub const POS_BONDING_PURSE: &str = "pos_bonding_purse"; +pub const POS_PAYMENT_PURSE: &str = "pos_payment_purse"; +pub const POS_REWARDS_PURSE: &str = "pos_rewards_purse"; + +pub enum GenesisResult { + RootNotFound, + KeyNotFound(Key), + TypeMismatch(TypeMismatch), + Serialization(bytesrepr::Error), + Success { + post_state_hash: Blake2bHash, + effect: ExecutionEffect, + }, +} + +impl fmt::Display for GenesisResult { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + GenesisResult::RootNotFound => write!(f, "Root not found"), + GenesisResult::KeyNotFound(key) => write!(f, "Key not found: {}", key), + GenesisResult::TypeMismatch(type_mismatch) => { + write!(f, "Type mismatch: {:?}", type_mismatch) + } + GenesisResult::Serialization(error) => write!(f, "Serialization error: {:?}", error), + GenesisResult::Success { + post_state_hash, + effect, + } => write!(f, "Success: {} {:?}", post_state_hash, effect), + } + } +} + +impl GenesisResult { + pub fn from_commit_result(commit_result: CommitResult, effect: ExecutionEffect) -> Self { + match commit_result { + CommitResult::RootNotFound => GenesisResult::RootNotFound, + CommitResult::KeyNotFound(key) => GenesisResult::KeyNotFound(key), + CommitResult::TypeMismatch(type_mismatch) => GenesisResult::TypeMismatch(type_mismatch), + CommitResult::Serialization(error) => GenesisResult::Serialization(error), + CommitResult::Success { state_root, .. } => GenesisResult::Success { + post_state_hash: state_root, + effect, + }, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct GenesisAccount { + account_hash: AccountHash, + balance: Motes, + bonded_amount: Motes, +} + +impl GenesisAccount { + pub fn new(account_hash: AccountHash, balance: Motes, bonded_amount: Motes) -> Self { + GenesisAccount { + account_hash, + balance, + bonded_amount, + } + } + + pub fn account_hash(&self) -> AccountHash { + self.account_hash + } + + pub fn balance(&self) -> Motes { + self.balance + } + + pub fn bonded_amount(&self) -> Motes { + self.bonded_amount + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> GenesisAccount { + let account_hash = AccountHash::new(rng.gen()); + + let mut u512_array = [0u8; 64]; + rng.fill_bytes(u512_array.as_mut()); + let balance = Motes::new(U512::from(u512_array.as_ref())); + + rng.fill_bytes(u512_array.as_mut()); + let bonded_amount = Motes::new(U512::from(u512_array.as_ref())); + + GenesisAccount { + account_hash, + balance, + bonded_amount, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GenesisConfig { + name: String, + timestamp: u64, + protocol_version: ProtocolVersion, + ee_config: ExecConfig, +} + +impl GenesisConfig { + #[allow(clippy::too_many_arguments)] + pub fn new( + name: String, + timestamp: u64, + protocol_version: ProtocolVersion, + ee_config: ExecConfig, + ) -> Self { + GenesisConfig { + name, + timestamp, + protocol_version, + ee_config, + } + } + + pub fn name(&self) -> &str { + self.name.as_str() + } + + pub fn timestamp(&self) -> u64 { + self.timestamp + } + + pub fn protocol_version(&self) -> ProtocolVersion { + self.protocol_version + } + + pub fn ee_config(&self) -> &ExecConfig { + &self.ee_config + } + + pub fn ee_config_mut(&mut self) -> &mut ExecConfig { + &mut self.ee_config + } + + pub fn take_ee_config(self) -> ExecConfig { + self.ee_config + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> GenesisConfig { + let count = rng.gen_range(1, 1000); + let name = iter::repeat(()) + .map(|_| rng.gen::()) + .take(count) + .collect(); + + let timestamp = rng.gen(); + + let protocol_version = ProtocolVersion::from_parts(rng.gen(), rng.gen(), rng.gen()); + + let ee_config = rng.gen(); + + GenesisConfig { + name, + timestamp, + protocol_version, + ee_config, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ExecConfig { + mint_installer_bytes: Vec, + proof_of_stake_installer_bytes: Vec, + standard_payment_installer_bytes: Vec, + accounts: Vec, + wasm_costs: WasmCosts, +} + +impl ExecConfig { + pub fn new( + mint_installer_bytes: Vec, + proof_of_stake_installer_bytes: Vec, + standard_payment_installer_bytes: Vec, + accounts: Vec, + wasm_costs: WasmCosts, + ) -> ExecConfig { + ExecConfig { + mint_installer_bytes, + proof_of_stake_installer_bytes, + standard_payment_installer_bytes, + accounts, + wasm_costs, + } + } + pub fn mint_installer_bytes(&self) -> &[u8] { + self.mint_installer_bytes.as_slice() + } + + pub fn proof_of_stake_installer_bytes(&self) -> &[u8] { + self.proof_of_stake_installer_bytes.as_slice() + } + + pub fn standard_payment_installer_bytes(&self) -> &[u8] { + self.standard_payment_installer_bytes.as_slice() + } + + pub fn wasm_costs(&self) -> WasmCosts { + self.wasm_costs + } + + pub fn get_bonded_validators(&self) -> impl Iterator + '_ { + let zero = Motes::zero(); + self.accounts.iter().filter_map(move |genesis_account| { + if genesis_account.bonded_amount() > zero { + Some(( + genesis_account.account_hash(), + genesis_account.bonded_amount(), + )) + } else { + None + } + }) + } + + pub fn accounts(&self) -> &[GenesisAccount] { + self.accounts.as_slice() + } + + pub fn push_account(&mut self, account: GenesisAccount) { + self.accounts.push(account) + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> ExecConfig { + let mut count = rng.gen_range(1000, 10_000); + let mint_installer_bytes = iter::repeat(()).map(|_| rng.gen()).take(count).collect(); + + count = rng.gen_range(1000, 10_000); + let proof_of_stake_installer_bytes = + iter::repeat(()).map(|_| rng.gen()).take(count).collect(); + + count = rng.gen_range(1000, 10_000); + let standard_payment_installer_bytes = + iter::repeat(()).map(|_| rng.gen()).take(count).collect(); + + count = rng.gen_range(1, 10); + let accounts = iter::repeat(()).map(|_| rng.gen()).take(count).collect(); + + let wasm_costs = WasmCosts { + regular: rng.gen(), + div: rng.gen(), + mul: rng.gen(), + mem: rng.gen(), + initial_mem: rng.gen(), + grow_mem: rng.gen(), + memcpy: rng.gen(), + max_stack_height: rng.gen(), + opcodes_mul: rng.gen(), + opcodes_div: rng.gen(), + }; + + ExecConfig { + mint_installer_bytes, + proof_of_stake_installer_bytes, + standard_payment_installer_bytes, + accounts, + wasm_costs, + } + } +} diff --git a/node/src/contract_core/engine_state/mod.rs b/node/src/contract_core/engine_state/mod.rs new file mode 100644 index 0000000000..522cee5280 --- /dev/null +++ b/node/src/contract_core/engine_state/mod.rs @@ -0,0 +1,1764 @@ +pub mod deploy_item; +pub mod engine_config; +mod error; +pub mod executable_deploy_item; +pub mod execute_request; +pub mod execution_effect; +pub mod execution_result; +pub mod genesis; +pub mod op; +pub mod query; +pub mod run_genesis_request; +pub mod system_contract_cache; +mod transfer; +pub mod upgrade; +pub mod utils; + +use std::{ + cell::RefCell, + collections::{BTreeMap, BTreeSet, HashMap}, + rc::Rc, +}; + +use num_traits::Zero; +use parity_wasm::elements::Module; +use tracing::{debug, warn}; + +use crate::contract_shared::wasm_costs::WasmCosts; +use crate::contract_shared::wasm_prep::{self, Preprocessor}; +use crate::contract_shared::{ + account::Account, + additive_map::AdditiveMap, + gas::Gas, + motes::Motes, + newtypes::{Blake2bHash, CorrelationId}, + stored_value::StoredValue, + transform::Transform, +}; +use crate::contract_storage::{ + global_state::{CommitResult, StateProvider, StateReader}, + protocol_data::ProtocolData, +}; +use types::{ + account::AccountHash, + bytesrepr::{self, ToBytes}, + contracts::{NamedKeys, ENTRY_POINT_NAME_INSTALL, UPGRADE_ENTRY_POINT_NAME}, + runtime_args, + system_contract_errors::mint, + system_contract_type::PROOF_OF_STAKE, + AccessRights, BlockTime, Contract, ContractHash, ContractPackage, ContractPackageHash, + ContractVersionKey, EntryPoint, EntryPointType, Key, Phase, ProtocolVersion, RuntimeArgs, URef, + U512, +}; + +pub use self::{ + engine_config::EngineConfig, + error::{Error, RootNotFound}, + transfer::TransferRuntimeArgsBuilder, +}; +use crate::contract_core::{ + engine_state::{ + deploy_item::DeployItem, + error::Error::MissingSystemContract, + executable_deploy_item::ExecutableDeployItem, + execute_request::ExecuteRequest, + execution_result::{ExecutionResult, ForcedTransferResult}, + genesis::{ + ExecConfig, GenesisAccount, GenesisResult, POS_PAYMENT_PURSE, POS_REWARDS_PURSE, + }, + query::{QueryRequest, QueryResult}, + system_contract_cache::SystemContractCache, + transfer::TransferTargetMode, + upgrade::{UpgradeConfig, UpgradeResult}, + }, + execution::{ + self, AddressGenerator, AddressGeneratorBuilder, DirectSystemContractCall, Executor, + }, + tracking_copy::{TrackingCopy, TrackingCopyExt}, +}; + +// TODO?: MAX_PAYMENT && CONV_RATE values are currently arbitrary w/ real values +// TBD gas * CONV_RATE = motes +pub const MAX_PAYMENT: u64 = 10_000_000; +pub const CONV_RATE: u64 = 10; + +pub const SYSTEM_ACCOUNT_ADDR: AccountHash = AccountHash::new([0u8; 32]); + +const GENESIS_INITIAL_BLOCKTIME: u64 = 0; +const ARG_AMOUNT: &str = "amount"; + +#[derive(Debug)] +pub struct EngineState { + config: EngineConfig, + system_contract_cache: SystemContractCache, + state: S, +} + +#[derive(Clone, Debug)] +pub enum GetModuleResult { + Session { + module: Module, + contract_package: ContractPackage, + entry_point: EntryPoint, + }, + Contract { + // Contract hash + base_key: Key, + module: Module, + contract: Contract, + contract_package: ContractPackage, + entry_point: EntryPoint, + }, +} + +impl GetModuleResult { + pub fn take_module(self) -> Module { + match self { + GetModuleResult::Session { module, .. } => module, + GetModuleResult::Contract { module, .. } => module, + } + } +} + +impl EngineState +where + S: StateProvider, + S::Error: Into, +{ + pub fn new(state: S, config: EngineConfig) -> EngineState { + let system_contract_cache = Default::default(); + EngineState { + config, + system_contract_cache, + state, + } + } + + pub fn config(&self) -> &EngineConfig { + &self.config + } + + pub fn wasm_costs( + &self, + protocol_version: ProtocolVersion, + ) -> Result, Error> { + match self.get_protocol_data(protocol_version)? { + Some(protocol_data) => Ok(Some(*protocol_data.wasm_costs())), + None => Ok(None), + } + } + + pub fn get_protocol_data( + &self, + protocol_version: ProtocolVersion, + ) -> Result, Error> { + match self.state.get_protocol_data(protocol_version) { + Ok(Some(protocol_data)) => Ok(Some(protocol_data)), + Err(error) => Err(Error::Exec(error.into())), + _ => Ok(None), + } + } + + pub fn commit_genesis( + &self, + correlation_id: CorrelationId, + genesis_config_hash: Blake2bHash, + protocol_version: ProtocolVersion, + ee_config: &ExecConfig, + ) -> Result { + // Preliminaries + let executor = Executor::new(self.config); + let blocktime = BlockTime::new(GENESIS_INITIAL_BLOCKTIME); + let gas_limit = Gas::new(std::u64::MAX.into()); + let phase = Phase::System; + + let initial_root_hash = self.state.empty_root(); + let wasm_costs = ee_config.wasm_costs(); + let preprocessor = Preprocessor::new(wasm_costs); + + // Spec #3: Create "virtual system account" object. + let mut virtual_system_account = { + let named_keys = NamedKeys::new(); + let purse = URef::new(Default::default(), AccessRights::READ_ADD_WRITE); + Account::create(SYSTEM_ACCOUNT_ADDR, named_keys, purse) + }; + + // Spec #4: Create a runtime. + let tracking_copy = match self.tracking_copy(initial_root_hash) { + Ok(Some(tracking_copy)) => Rc::new(RefCell::new(tracking_copy)), + Ok(None) => panic!("state has not been initialized properly"), + Err(error) => return Err(error), + }; + + // Persist the "virtual system account". It will get overwritten with the actual system + // account below. + let key = Key::Account(SYSTEM_ACCOUNT_ADDR); + let value = { + let virtual_system_account = virtual_system_account.clone(); + StoredValue::Account(virtual_system_account) + }; + + tracking_copy.borrow_mut().write(key, value); + + // Spec #4A: random number generator is seeded from the hash of GenesisConfig.name + // Updated: random number generator is seeded from genesis_config_hash from the RunGenesis + // RPC call + + let hash_address_generator = { + let generator = AddressGenerator::new(&genesis_config_hash.value(), phase); + Rc::new(RefCell::new(generator)) + }; + let uref_address_generator = { + let generator = AddressGenerator::new(&genesis_config_hash.value(), phase); + Rc::new(RefCell::new(generator)) + }; + + // Spec #5: Execute the wasm code from the mint installer bytes + let (mint_package_hash, mint_hash): (ContractPackageHash, ContractHash) = { + let mint_installer_bytes = ee_config.mint_installer_bytes(); + let mint_installer_module = preprocessor.preprocess(mint_installer_bytes)?; + let args = RuntimeArgs::new(); + let authorization_keys: BTreeSet = BTreeSet::new(); + let install_deploy_hash = genesis_config_hash.into(); + let hash_address_generator = Rc::clone(&hash_address_generator); + let uref_address_generator = Rc::clone(&uref_address_generator); + let tracking_copy = Rc::clone(&tracking_copy); + let system_contract_cache = SystemContractCache::clone(&self.system_contract_cache); + let protocol_data = ProtocolData::default(); + + executor.exec_wasm_direct( + mint_installer_module, + ENTRY_POINT_NAME_INSTALL, + args, + &mut virtual_system_account, + authorization_keys, + blocktime, + install_deploy_hash, + gas_limit, + hash_address_generator, + uref_address_generator, + protocol_version, + correlation_id, + tracking_copy, + phase, + protocol_data, + system_contract_cache, + )? + }; + + // Spec #7: Execute pos installer wasm code, passing the initially bonded validators as an + // argument + let (_proof_of_stake_package_hash, proof_of_stake_hash): ( + ContractPackageHash, + ContractHash, + ) = { + // Spec #6: Compute initially bonded validators as the contents of accounts_path + // filtered to non-zero staked amounts. + let bonded_validators: BTreeMap = ee_config + .get_bonded_validators() + .map(|(k, v)| (k, v.value())) + .collect(); + + let tracking_copy = Rc::clone(&tracking_copy); + let hash_address_generator = Rc::clone(&hash_address_generator); + let uref_address_generator = Rc::clone(&uref_address_generator); + let install_deploy_hash = genesis_config_hash.into(); + let system_contract_cache = SystemContractCache::clone(&self.system_contract_cache); + + // Constructs a partial protocol data with already known uref to pass the validation + // step + let partial_protocol_data = ProtocolData::partial_with_mint(mint_hash); + + let proof_of_stake_installer_bytes = ee_config.proof_of_stake_installer_bytes(); + let proof_of_stake_installer_module = + preprocessor.preprocess(proof_of_stake_installer_bytes)?; + let args = runtime_args! { + "mint_contract_package_hash" => mint_package_hash, + "genesis_validators" => bonded_validators, + }; + let authorization_keys: BTreeSet = BTreeSet::new(); + + executor.exec_wasm_direct( + proof_of_stake_installer_module, + ENTRY_POINT_NAME_INSTALL, + args, + &mut virtual_system_account, + authorization_keys, + blocktime, + install_deploy_hash, + gas_limit, + hash_address_generator, + uref_address_generator, + protocol_version, + correlation_id, + tracking_copy, + phase, + partial_protocol_data, + system_contract_cache, + )? + }; + + // Execute standard payment installer wasm code + // + // Note: this deviates from the implementation strategy described in the original + // specification. + let protocol_data = ProtocolData::partial_without_standard_payment( + wasm_costs, + mint_hash, + proof_of_stake_hash, + ); + + let standard_payment_hash: ContractHash = { + let standard_payment_installer_bytes = { + // NOTE: Before integration node wasn't updated to pass the bytes, so we were bundling it. This debug_assert can be removed once integration with genesis works. + debug_assert!( + !ee_config.standard_payment_installer_bytes().is_empty(), + "Caller is required to pass the standard_payment_installer bytes" + ); + &ee_config.standard_payment_installer_bytes() + }; + + let standard_payment_installer_module = + preprocessor.preprocess(standard_payment_installer_bytes)?; + let args = RuntimeArgs::new(); + let authorization_keys = BTreeSet::new(); + let install_deploy_hash = genesis_config_hash.into(); + let hash_address_generator = Rc::clone(&hash_address_generator); + let uref_address_generator = Rc::clone(&uref_address_generator); + let tracking_copy = Rc::clone(&tracking_copy); + let system_contract_cache = SystemContractCache::clone(&self.system_contract_cache); + + executor.exec_wasm_direct( + standard_payment_installer_module, + ENTRY_POINT_NAME_INSTALL, + args, + &mut virtual_system_account, + authorization_keys, + blocktime, + install_deploy_hash, + gas_limit, + hash_address_generator, + uref_address_generator, + protocol_version, + correlation_id, + tracking_copy, + phase, + protocol_data, + system_contract_cache, + )? + }; + + // Spec #2: Associate given CostTable with given ProtocolVersion. + let protocol_data = ProtocolData::new( + wasm_costs, + mint_hash, + proof_of_stake_hash, + standard_payment_hash, + ); + + self.state + .put_protocol_data(protocol_version, &protocol_data) + .map_err(Into::into)?; + + // + // NOTE: The following stanzas deviate from the implementation strategy described in the + // original specification. + // + // It has the following benefits over that approach: + // * It does not make an intermediate commit + // * The system account never holds funds + // * Similarly, the system account does not need to be handled differently than a normal + // account (with the exception of its known keys) + // + // Create known keys for chainspec accounts + let account_named_keys = NamedKeys::new(); + + // Create accounts + { + // Collect chainspec accounts and their known keys with the genesis account and its + // known keys + let accounts = { + let mut ret: Vec<(GenesisAccount, NamedKeys)> = ee_config + .accounts() + .to_vec() + .into_iter() + .map(|account| (account, account_named_keys.clone())) + .collect(); + let system_account = + GenesisAccount::new(SYSTEM_ACCOUNT_ADDR, Motes::zero(), Motes::zero()); + ret.push((system_account, virtual_system_account.named_keys().clone())); + ret + }; + + // Get the mint module + let module = { + let contract = tracking_copy + .borrow_mut() + .get_contract(correlation_id, mint_hash)?; + + let contract_wasm = tracking_copy + .borrow_mut() + .get_contract_wasm(correlation_id, contract.contract_wasm_hash())?; + let bytes = contract_wasm.bytes(); + wasm_prep::deserialize(&bytes)? + }; + // For each account... + for (account, named_keys) in accounts.into_iter() { + let module = module.clone(); + let args = runtime_args! { + ARG_AMOUNT => account.balance().value(), + }; + let tracking_copy_exec = Rc::clone(&tracking_copy); + let tracking_copy_write = Rc::clone(&tracking_copy); + let mut named_keys_exec = NamedKeys::new(); + let base_key = mint_hash; + let authorization_keys: BTreeSet = BTreeSet::new(); + let account_hash = account.account_hash(); + let purse_creation_deploy_hash = account_hash.value(); + let hash_address_generator = Rc::clone(&hash_address_generator); + let uref_address_generator = { + let generator = AddressGeneratorBuilder::new() + .seed_with(&genesis_config_hash.value()) + .seed_with(&account_hash.to_bytes()?) + .seed_with(&[phase as u8]) + .build(); + Rc::new(RefCell::new(generator)) + }; + let system_contract_cache = SystemContractCache::clone(&self.system_contract_cache); + + let mint_result: Result = { + // ...call the Mint's "mint" endpoint to create purse with tokens... + let (_instance, mut runtime) = executor.create_runtime( + module, + EntryPointType::Contract, + args.clone(), + &mut named_keys_exec, + Default::default(), + base_key.into(), + &virtual_system_account, + authorization_keys, + blocktime, + purse_creation_deploy_hash, + gas_limit, + hash_address_generator, + uref_address_generator, + protocol_version, + correlation_id, + tracking_copy_exec, + phase, + protocol_data, + system_contract_cache, + )?; + + runtime + .call_versioned_contract( + mint_package_hash, + Some(1), + "mint".to_string(), + args, + )? + .into_t::>() + .expect("should convert") + }; + + // ...and write that account to global state... + let key = Key::Account(account_hash); + let value = { + let main_purse = mint_result?; + StoredValue::Account(Account::create(account_hash, named_keys, main_purse)) + }; + + tracking_copy_write.borrow_mut().write(key, value); + } + } + // Spec #15: Commit the transforms. + let effects = tracking_copy.borrow().effect(); + + let commit_result = self + .state + .commit( + correlation_id, + initial_root_hash, + effects.transforms.to_owned(), + ) + .map_err(Into::into)?; + + // Return the result + let genesis_result = GenesisResult::from_commit_result(commit_result, effects); + + Ok(genesis_result) + } + + pub fn commit_upgrade( + &self, + correlation_id: CorrelationId, + upgrade_config: UpgradeConfig, + ) -> Result { + // per specification: + // https://casperlabs.atlassian.net/wiki/spaces/EN/pages/139854367/Upgrading+System+Contracts+Specification + + // 3.1.1.1.1.1 validate pre state hash exists + // 3.1.2.1 get a tracking_copy at the provided pre_state_hash + let pre_state_hash = upgrade_config.pre_state_hash(); + let tracking_copy = match self.tracking_copy(pre_state_hash)? { + Some(tracking_copy) => Rc::new(RefCell::new(tracking_copy)), + None => return Ok(UpgradeResult::RootNotFound), + }; + + // 3.1.1.1.1.2 current protocol version is required + let current_protocol_version = upgrade_config.current_protocol_version(); + let current_protocol_data = match self.state.get_protocol_data(current_protocol_version) { + Ok(Some(protocol_data)) => protocol_data, + Ok(None) => { + return Err(Error::InvalidProtocolVersion(current_protocol_version)); + } + Err(error) => { + return Err(Error::Exec(error.into())); + } + }; + + // 3.1.1.1.1.3 activation point is not currently used by EE; skipping + // 3.1.1.1.1.4 upgrade point protocol version validation + let new_protocol_version = upgrade_config.new_protocol_version(); + + let upgrade_check_result = + current_protocol_version.check_next_version(&new_protocol_version); + + if upgrade_check_result.is_invalid() { + return Err(Error::InvalidProtocolVersion(new_protocol_version)); + } + + // 3.1.1.1.1.6 resolve wasm CostTable for new protocol version + let new_wasm_costs = match upgrade_config.wasm_costs() { + Some(new_wasm_costs) => new_wasm_costs, + None => *current_protocol_data.wasm_costs(), + }; + + // 3.1.2.2 persist wasm CostTable + let mut new_protocol_data = ProtocolData::new( + new_wasm_costs, + current_protocol_data.mint(), + current_protocol_data.proof_of_stake(), + current_protocol_data.standard_payment(), + ); + + self.state + .put_protocol_data(new_protocol_version, &new_protocol_data) + .map_err(Into::into)?; + + // 3.1.1.1.1.5 upgrade installer is optional except on major version upgrades + match upgrade_config.upgrade_installer_bytes() { + None if upgrade_check_result.is_code_required() => { + // 3.1.1.1.1.5 code is required for major version bump + return Err(Error::InvalidUpgradeConfig); + } + None => { + // optional for patch/minor bumps + } + Some(bytes) => { + // 3.1.2.3 execute upgrade installer if one is provided + + // preprocess installer module + let upgrade_installer_module = { + let preprocessor = Preprocessor::new(new_wasm_costs); + preprocessor.preprocess(bytes)? + }; + + // currently there are no expected args for an upgrade installer but args are + // supported + let args = match upgrade_config.upgrade_installer_args() { + Some(args) => { + bytesrepr::deserialize(args.to_vec()).expect("should deserialize") + } + None => RuntimeArgs::new(), + }; + + // execute as system account + let mut system_account = { + let key = Key::Account(SYSTEM_ACCOUNT_ADDR); + match tracking_copy.borrow_mut().read(correlation_id, &key) { + Ok(Some(StoredValue::Account(account))) => account, + Ok(_) => panic!("system account must exist"), + Err(error) => return Err(Error::Exec(error.into())), + } + }; + + let authorization_keys = { + let mut ret = BTreeSet::new(); + ret.insert(SYSTEM_ACCOUNT_ADDR); + ret + }; + + let blocktime = BlockTime::default(); + + let deploy_hash = { + // seeds address generator w/ protocol version + let bytes: Vec = upgrade_config + .new_protocol_version() + .value() + .into_bytes()? + .to_vec(); + Blake2bHash::new(&bytes).into() + }; + + // upgrade has no gas limit; approximating with MAX + let gas_limit = Gas::new(std::u64::MAX.into()); + let phase = Phase::System; + let hash_address_generator = { + let generator = AddressGenerator::new(&pre_state_hash.value(), phase); + Rc::new(RefCell::new(generator)) + }; + let uref_address_generator = { + let generator = AddressGenerator::new(&pre_state_hash.value(), phase); + Rc::new(RefCell::new(generator)) + }; + let tracking_copy = Rc::clone(&tracking_copy); + let system_contract_cache = SystemContractCache::clone(&self.system_contract_cache); + + let executor = Executor::new(self.config); + + let result: BTreeMap = executor.exec_wasm_direct( + upgrade_installer_module, + UPGRADE_ENTRY_POINT_NAME, + args, + &mut system_account, + authorization_keys, + blocktime, + deploy_hash, + gas_limit, + hash_address_generator, + uref_address_generator, + new_protocol_version, + correlation_id, + Rc::clone(&tracking_copy), + phase, + new_protocol_data, + system_contract_cache, + )?; + + if !new_protocol_data.update_from(result) { + return Err(Error::InvalidUpgradeResult); + } else { + self.state + .put_protocol_data(new_protocol_version, &new_protocol_data) + .map_err(Into::into)?; + } + } + } + + let effects = tracking_copy.borrow().effect(); + + // commit + let commit_result = self + .state + .commit( + correlation_id, + pre_state_hash, + effects.transforms.to_owned(), + ) + .map_err(Into::into)?; + + // return result and effects + Ok(UpgradeResult::from_commit_result(commit_result, effects)) + } + + pub fn tracking_copy( + &self, + hash: Blake2bHash, + ) -> Result>, Error> { + match self.state.checkout(hash).map_err(Into::into)? { + Some(tc) => Ok(Some(TrackingCopy::new(tc))), + None => Ok(None), + } + } + + pub fn run_query( + &self, + correlation_id: CorrelationId, + query_request: QueryRequest, + ) -> Result { + let tracking_copy = match self.tracking_copy(query_request.state_hash())? { + Some(tracking_copy) => Rc::new(RefCell::new(tracking_copy)), + None => return Ok(QueryResult::RootNotFound), + }; + + let tracking_copy = tracking_copy.borrow(); + + Ok(tracking_copy + .query(correlation_id, query_request.key(), query_request.path()) + .map_err(|err| Error::Exec(err.into()))? + .into()) + } + + pub fn run_execute( + &self, + correlation_id: CorrelationId, + mut exec_request: ExecuteRequest, + ) -> Result, RootNotFound> { + // TODO: do not unwrap + let wasm_costs = self + .wasm_costs(exec_request.protocol_version) + .unwrap() + .unwrap(); + let executor = Executor::new(self.config); + let preprocessor = Preprocessor::new(wasm_costs); + + let mut results = Vec::new(); + + for deploy_item in exec_request.take_deploys() { + let result = match deploy_item { + Err(exec_result) => Ok(exec_result), + Ok(deploy_item) => match deploy_item.session { + ExecutableDeployItem::Transfer { .. } => self.transfer( + correlation_id, + &executor, + &preprocessor, + exec_request.protocol_version, + exec_request.parent_state_hash, + BlockTime::new(exec_request.block_time), + deploy_item, + ), + _ => self.deploy( + correlation_id, + &executor, + &preprocessor, + exec_request.protocol_version, + exec_request.parent_state_hash, + BlockTime::new(exec_request.block_time), + deploy_item, + ), + }, + }; + match result { + Ok(result) => results.push(result), + Err(error) => { + return Err(error); + } + }; + } + + Ok(results) + } + + pub fn get_module( + &self, + tracking_copy: Rc::Reader>>>, + deploy_item: &ExecutableDeployItem, + account: &Account, + correlation_id: CorrelationId, + preprocessor: &Preprocessor, + protocol_version: &ProtocolVersion, + ) -> Result { + let (contract_package, contract, base_key) = match deploy_item { + ExecutableDeployItem::ModuleBytes { module_bytes, .. } => { + let module = preprocessor.preprocess(&module_bytes)?; + return Ok(GetModuleResult::Session { + module, + contract_package: ContractPackage::default(), + entry_point: EntryPoint::default(), + }); + } + ExecutableDeployItem::StoredContractByHash { .. } + | ExecutableDeployItem::StoredContractByName { .. } => { + let stored_contract_key = deploy_item.to_contract_hash_key(&account)?.unwrap(); + + let contract = tracking_copy + .borrow_mut() + .get_contract(correlation_id, stored_contract_key.into_hash().unwrap())?; + + if !contract.is_compatible_protocol_version(*protocol_version) { + let exec_error = execution::Error::IncompatibleProtocolMajorVersion { + expected: protocol_version.value().major, + actual: contract.protocol_version().value().major, + }; + return Err(error::Error::Exec(exec_error)); + } + + let contract_package = tracking_copy + .borrow_mut() + .get_contract_package(correlation_id, contract.contract_package_hash())?; + + (contract_package, contract, stored_contract_key) + } + ExecutableDeployItem::StoredVersionedContractByName { version, .. } + | ExecutableDeployItem::StoredVersionedContractByHash { version, .. } => { + let contract_package_key = deploy_item.to_contract_hash_key(&account)?.unwrap(); + let contract_package_hash = contract_package_key.into_seed(); + + let contract_package = tracking_copy + .borrow_mut() + .get_contract_package(correlation_id, contract_package_hash)?; + + let maybe_version_key = + version.map(|ver| ContractVersionKey::new(protocol_version.value().major, ver)); + + let contract_version_key = maybe_version_key + .or_else(|| contract_package.current_contract_version()) + .ok_or_else(|| { + error::Error::Exec(execution::Error::NoActiveContractVersions( + contract_package_hash, + )) + })?; + + if !contract_package.is_version_enabled(contract_version_key) { + return Err(error::Error::Exec( + execution::Error::InvalidContractVersion(contract_version_key), + )); + } + + let contract_hash = *contract_package + .lookup_contract_hash(contract_version_key) + .ok_or_else(|| { + error::Error::Exec(execution::Error::InvalidContractVersion( + contract_version_key, + )) + })?; + + let contract = tracking_copy + .borrow_mut() + .get_contract(correlation_id, contract_hash)?; + + (contract_package, contract, contract_package_key) + } + ExecutableDeployItem::Transfer { .. } => { + return Err(error::Error::InvalidDeployItemVariant(String::from( + "Transfer", + ))) + } + }; + + let entry_point_name = deploy_item.entry_point_name(); + + let entry_point = contract + .entry_point(entry_point_name) + .cloned() + .ok_or_else(|| { + error::Error::Exec(execution::Error::NoSuchMethod(entry_point_name.to_owned())) + })?; + + let contract_wasm = tracking_copy + .borrow_mut() + .get_contract_wasm(correlation_id, contract.contract_wasm_hash())?; + + let module = wasm_prep::deserialize(contract_wasm.bytes())?; + + match entry_point.entry_point_type() { + EntryPointType::Session => Ok(GetModuleResult::Session { + module, + contract_package, + entry_point, + }), + EntryPointType::Contract => Ok(GetModuleResult::Contract { + module, + base_key, + contract, + contract_package, + entry_point, + }), + } + } + + fn get_module_from_contract_hash( + &self, + tracking_copy: Rc::Reader>>>, + contract_hash: ContractHash, + correlation_id: CorrelationId, + protocol_version: &ProtocolVersion, + ) -> Result { + let contract = tracking_copy + .borrow_mut() + .get_contract(correlation_id, contract_hash)?; + + // A contract may only call a stored contract that has the same protocol major version + // number. + if !contract.is_compatible_protocol_version(*protocol_version) { + let exec_error = execution::Error::IncompatibleProtocolMajorVersion { + expected: protocol_version.value().major, + actual: contract.protocol_version().value().major, + }; + return Err(error::Error::Exec(exec_error)); + } + + let contract_wasm = tracking_copy + .borrow_mut() + .get_contract_wasm(correlation_id, contract.contract_wasm_hash())?; + + let module = wasm_prep::deserialize(contract_wasm.bytes())?; + + Ok(module) + } + + fn get_authorized_account( + &self, + correlation_id: CorrelationId, + account_hash: AccountHash, + authorization_keys: &BTreeSet, + tracking_copy: Rc::Reader>>>, + ) -> Result { + let account: Account = match tracking_copy + .borrow_mut() + .get_account(correlation_id, account_hash) + { + Ok(account) => account, + Err(_) => { + return Err(error::Error::Authorization); + } + }; + + // Authorize using provided authorization keys + if !account.can_authorize(authorization_keys) { + return Err(error::Error::Authorization); + } + + // Check total key weight against deploy threshold + if !account.can_deploy_with(authorization_keys) { + return Err(execution::Error::DeploymentAuthorizationFailure.into()); + } + + Ok(account) + } + + #[allow(clippy::too_many_arguments)] + pub fn transfer( + &self, + correlation_id: CorrelationId, + executor: &Executor, + preprocessor: &Preprocessor, + protocol_version: ProtocolVersion, + prestate_hash: Blake2bHash, + blocktime: BlockTime, + deploy_item: DeployItem, + ) -> Result { + let protocol_data = match self.state.get_protocol_data(protocol_version) { + Ok(Some(protocol_data)) => protocol_data, + Ok(None) => { + let error = Error::InvalidProtocolVersion(protocol_version); + return Ok(ExecutionResult::precondition_failure(error)); + } + Err(error) => { + return Ok(ExecutionResult::precondition_failure(Error::Exec( + error.into(), + ))); + } + }; + + let tracking_copy = match self.tracking_copy(prestate_hash) { + Err(error) => return Ok(ExecutionResult::precondition_failure(error)), + Ok(None) => return Err(RootNotFound::new(prestate_hash)), + Ok(Some(tracking_copy)) => Rc::new(RefCell::new(tracking_copy)), + }; + + let base_key = Key::Account(deploy_item.address); + + let account_public_key = match base_key.into_account() { + Some(account_addr) => account_addr, + None => { + return Ok(ExecutionResult::precondition_failure( + error::Error::Authorization, + )); + } + }; + + let authorization_keys = deploy_item.authorization_keys; + + let account = match self.get_authorized_account( + correlation_id, + account_public_key, + &authorization_keys, + Rc::clone(&tracking_copy), + ) { + Ok(account) => account, + Err(e) => return Ok(ExecutionResult::precondition_failure(e)), + }; + + let mint_contract = match tracking_copy + .borrow_mut() + .get_contract(correlation_id, protocol_data.mint()) + { + Ok(contract) => contract, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error.into())); + } + }; + + let mint_module = { + let contract_wasm_hash = mint_contract.contract_wasm_hash(); + let use_system_contracts = self.config.use_system_contracts(); + match tracking_copy.borrow_mut().get_system_module( + correlation_id, + contract_wasm_hash, + use_system_contracts, + preprocessor, + ) { + Ok(module) => module, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error.into())); + } + } + }; + + let mut named_keys = mint_contract.named_keys().to_owned(); + let mut extra_keys: Vec = vec![]; + let base_key = Key::from(protocol_data.mint()); + let gas_limit = Gas::new(U512::from(std::u64::MAX)); + + let input_runtime_args = match deploy_item.session.into_runtime_args() { + Ok(runtime_args) => runtime_args, + Err(error) => return Ok(ExecutionResult::precondition_failure(error.into())), + }; + + let mut runtime_args_builder = TransferRuntimeArgsBuilder::new(input_runtime_args); + match runtime_args_builder.transfer_target_mode(correlation_id, Rc::clone(&tracking_copy)) { + Ok(mode) => match mode { + TransferTargetMode::Unknown | TransferTargetMode::PurseExists(_) => { /* noop */ } + TransferTargetMode::CreateAccount(public_key) => { + let (maybe_uref, execution_result): (Option, ExecutionResult) = executor + .exec_system_contract( + DirectSystemContractCall::CreatePurse, + mint_module.clone(), + runtime_args! {}, // mint create takes no arguments + &mut named_keys, + Default::default(), + base_key, + &account, + authorization_keys.clone(), + blocktime, + deploy_item.deploy_hash, + gas_limit, + protocol_version, + correlation_id, + Rc::clone(&tracking_copy), + Phase::Session, + protocol_data, + SystemContractCache::clone(&self.system_contract_cache), + ); + match maybe_uref { + Some(main_purse) => { + let new_account = + Account::create(public_key, Default::default(), main_purse); + extra_keys.push(Key::from(main_purse)); + // write new account + tracking_copy + .borrow_mut() + .write(Key::Account(public_key), StoredValue::Account(new_account)) + } + None => { + return Ok(execution_result); + } + } + } + }, + Err(error) => { + return Ok(ExecutionResult::Failure { + error, + effect: Default::default(), + cost: Gas::default(), + }); + } + } + + let runtime_args = + match runtime_args_builder.build(&account, correlation_id, Rc::clone(&tracking_copy)) { + Ok(runtime_args) => runtime_args, + Err(error) => { + return Ok(ExecutionResult::Failure { + error, + effect: Default::default(), + cost: Gas::default(), + }); + } + }; + + let (_, execution_result): (Option>, ExecutionResult) = executor + .exec_system_contract( + DirectSystemContractCall::Transfer, + mint_module, + runtime_args, + &mut named_keys, + extra_keys.as_slice(), + base_key, + &account, + authorization_keys, + blocktime, + deploy_item.deploy_hash, + gas_limit, + protocol_version, + correlation_id, + tracking_copy, + Phase::Session, + protocol_data, + SystemContractCache::clone(&self.system_contract_cache), + ); + + Ok(execution_result) + } + + #[allow(clippy::too_many_arguments)] + pub fn deploy( + &self, + correlation_id: CorrelationId, + executor: &Executor, + preprocessor: &Preprocessor, + protocol_version: ProtocolVersion, + prestate_hash: Blake2bHash, + blocktime: BlockTime, + deploy_item: DeployItem, + ) -> Result { + // spec: https://casperlabs.atlassian.net/wiki/spaces/EN/pages/123404576/Payment+code+execution+specification + + // Obtain current protocol data for given version + // do this first, as there is no reason to proceed if protocol version is invalid + let protocol_data = match self.state.get_protocol_data(protocol_version) { + Ok(Some(protocol_data)) => protocol_data, + Ok(None) => { + let error = Error::InvalidProtocolVersion(protocol_version); + return Ok(ExecutionResult::precondition_failure(error)); + } + Err(error) => { + return Ok(ExecutionResult::precondition_failure(Error::Exec( + error.into(), + ))); + } + }; + + // Create tracking copy (which functions as a deploy context) + // validation_spec_2: prestate_hash check + // do this second; as there is no reason to proceed if the prestate hash is invalid + let tracking_copy = match self.tracking_copy(prestate_hash) { + Err(error) => return Ok(ExecutionResult::precondition_failure(error)), + Ok(None) => return Err(RootNotFound::new(prestate_hash)), + Ok(Some(tracking_copy)) => Rc::new(RefCell::new(tracking_copy)), + }; + + let base_key = Key::Account(deploy_item.address); + + // Get addr bytes from `address` (which is actually a Key) + // validation_spec_3: account validity + let account_public_key = match base_key.into_account() { + Some(account_addr) => account_addr, + None => { + return Ok(ExecutionResult::precondition_failure( + error::Error::Authorization, + )); + } + }; + + let authorization_keys = deploy_item.authorization_keys; + + // Get account from tracking copy + // validation_spec_3: account validity + let account = match self.get_authorized_account( + correlation_id, + account_public_key, + &authorization_keys, + Rc::clone(&tracking_copy), + ) { + Ok(account) => account, + Err(e) => return Ok(ExecutionResult::precondition_failure(e)), + }; + + let session = deploy_item.session; + let payment = deploy_item.payment; + let deploy_hash = deploy_item.deploy_hash; + + // Create session code `A` from provided session bytes + // validation_spec_1: valid wasm bytes + // we do this upfront as there is no reason to continue if session logic is invalid + let session_module = match self.get_module( + Rc::clone(&tracking_copy), + &session, + &account, + correlation_id, + preprocessor, + &protocol_version, + ) { + Ok(module) => module, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error)); + } + }; + + // Get mint system contract details + // payment_code_spec_6: system contract validity + let mint_hash = protocol_data.mint(); + + let mint_contract = match tracking_copy + .borrow_mut() + .get_contract(correlation_id, mint_hash) + { + Ok(contract) => contract, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error.into())); + } + }; + + // cache mint module + if !self.system_contract_cache.has(mint_hash) { + let mint_module = match tracking_copy.borrow_mut().get_system_module( + correlation_id, + mint_contract.contract_wasm_hash(), + self.config.use_system_contracts(), + preprocessor, + ) { + Ok(contract) => contract, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error.into())); + } + }; + + self.system_contract_cache.insert(mint_hash, mint_module); + } + + // Get proof of stake system contract URef from account (an account on a + // different network may have a pos contract other than the CLPoS) + // payment_code_spec_6: system contract validity + let proof_of_stake_hash = protocol_data.proof_of_stake(); + + // Get proof of stake system contract details + // payment_code_spec_6: system contract validity + let proof_of_stake_contract = match tracking_copy + .borrow_mut() + .get_contract(correlation_id, proof_of_stake_hash) + { + Ok(contract) => contract, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error.into())); + } + }; + + let proof_of_stake_module = match tracking_copy.borrow_mut().get_system_module( + correlation_id, + proof_of_stake_contract.contract_wasm_hash(), + self.config.use_system_contracts(), + preprocessor, + ) { + Ok(module) => module, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error.into())); + } + }; + + // cache proof_of_stake module + if !self.system_contract_cache.has(proof_of_stake_hash) { + self.system_contract_cache + .insert(proof_of_stake_hash, proof_of_stake_module.clone()); + } + + // Get account main purse balance key + // validation_spec_5: account main purse minimum balance + let account_main_purse_balance_key: Key = { + let account_key = Key::URef(account.main_purse()); + match tracking_copy + .borrow_mut() + .get_purse_balance_key(correlation_id, account_key) + { + Ok(key) => key, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error.into())); + } + } + }; + + // Get account main purse balance to enforce precondition and in case of forced + // transfer validation_spec_5: account main purse minimum balance + let account_main_purse_balance: Motes = match tracking_copy + .borrow_mut() + .get_purse_balance(correlation_id, account_main_purse_balance_key) + { + Ok(balance) => balance, + Err(error) => return Ok(ExecutionResult::precondition_failure(error.into())), + }; + + let max_payment_cost: Motes = Motes::new(U512::from(MAX_PAYMENT)); + + // Enforce minimum main purse balance validation + // validation_spec_5: account main purse minimum balance + if account_main_purse_balance < max_payment_cost { + return Ok(ExecutionResult::precondition_failure( + Error::InsufficientPayment, + )); + } + + // Finalization is executed by system account (currently genesis account) + // payment_code_spec_5: system executes finalization + let system_account = Account::new( + SYSTEM_ACCOUNT_ADDR, + Default::default(), + URef::new(Default::default(), AccessRights::READ_ADD_WRITE), + Default::default(), + Default::default(), + ); + + // [`ExecutionResultBuilder`] handles merging of multiple execution results + let mut execution_result_builder = execution_result::ExecutionResultBuilder::new(); + + // Execute provided payment code + let payment_result = { + // payment_code_spec_1: init pay environment w/ gas limit == (max_payment_cost / + // conv_rate) + let pay_gas_limit = Gas::from_motes(max_payment_cost, CONV_RATE).unwrap_or_default(); + + let module_bytes_is_empty = match payment { + ExecutableDeployItem::ModuleBytes { + ref module_bytes, .. + } => module_bytes.is_empty(), + _ => false, + }; + + // Create payment code module from bytes + // validation_spec_1: valid wasm bytes + let maybe_payment_module = if module_bytes_is_empty { + let standard_payment_hash: ContractHash = + match self.state.get_protocol_data(protocol_version) { + Ok(Some(protocol_data)) => protocol_data.standard_payment(), + Ok(None) => { + return Ok(ExecutionResult::precondition_failure( + Error::InvalidProtocolVersion(protocol_version), + )); + } + Err(_) => return Ok(ExecutionResult::precondition_failure(Error::Deploy)), + }; + + // if "use-system-contracts" is false, "do_nothing" wasm is returned + self.get_module_from_contract_hash( + Rc::clone(&tracking_copy), + standard_payment_hash, + correlation_id, + &protocol_version, + ) + .map(|module| GetModuleResult::Session { + module, + contract_package: ContractPackage::default(), + entry_point: EntryPoint::default(), + }) + } else { + self.get_module( + Rc::clone(&tracking_copy), + &payment, + &account, + correlation_id, + preprocessor, + &protocol_version, + ) + }; + + let payment_module = match maybe_payment_module { + Ok(module) => module, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error)); + } + }; + + // payment_code_spec_2: execute payment code + let phase = Phase::Payment; + let ( + payment_module, + payment_base_key, + mut payment_named_keys, + payment_package, + payment_entry_point, + ) = match payment_module { + GetModuleResult::Session { + module, + contract_package, + entry_point, + } => ( + module, + base_key, + account.named_keys().clone(), + contract_package, + entry_point, + ), + GetModuleResult::Contract { + module, + base_key, + contract, + contract_package, + entry_point, + } => ( + module, + base_key, + contract.named_keys().clone(), + contract_package, + entry_point, + ), + }; + + let payment_args = match payment.into_runtime_args() { + Ok(args) => args, + Err(e) => { + let exec_err: execution::Error = e.into(); + warn!("Unable to deserialize arguments: {:?}", exec_err); + return Ok(ExecutionResult::precondition_failure(exec_err.into())); + } + }; + + let system_contract_cache = SystemContractCache::clone(&self.system_contract_cache); + + if self.config.use_system_contracts() || !module_bytes_is_empty { + executor.exec( + payment_module, + payment_entry_point, + payment_args, + payment_base_key, + &account, + &mut payment_named_keys, + authorization_keys.clone(), + blocktime, + deploy_hash, + pay_gas_limit, + protocol_version, + correlation_id, + Rc::clone(&tracking_copy), + phase, + protocol_data, + system_contract_cache, + &payment_package, + ) + } else { + // use host side standard payment + let hash_address_generator = { + let generator = AddressGenerator::new(&deploy_hash, phase); + Rc::new(RefCell::new(generator)) + }; + let uref_address_generator = { + let generator = AddressGenerator::new(&deploy_hash, phase); + Rc::new(RefCell::new(generator)) + }; + + let mut runtime = match executor.create_runtime( + payment_module, + EntryPointType::Session, + payment_args, + &mut payment_named_keys, + Default::default(), + payment_base_key, + &account, + authorization_keys.clone(), + blocktime, + deploy_hash, + pay_gas_limit, + hash_address_generator, + uref_address_generator, + protocol_version, + correlation_id, + Rc::clone(&tracking_copy), + phase, + protocol_data, + system_contract_cache, + ) { + Ok((_instance, runtime)) => runtime, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(Error::Exec(error))); + } + }; + + let effects_snapshot = tracking_copy.borrow().effect(); + + match runtime.call_host_standard_payment() { + Ok(()) => ExecutionResult::Success { + effect: runtime.context().effect(), + cost: runtime.context().gas_counter(), + }, + Err(error) => ExecutionResult::Failure { + error: error.into(), + effect: effects_snapshot, + cost: runtime.context().gas_counter(), + }, + } + } + }; + + debug!("Payment result: {:?}", payment_result); + + let payment_result_cost = payment_result.cost(); + // payment_code_spec_3: fork based upon payment purse balance and cost of + // payment code execution + let payment_purse_balance: Motes = { + // Get payment purse Key from proof of stake contract + // payment_code_spec_6: system contract validity + let payment_purse_key: Key = + match proof_of_stake_contract.named_keys().get(POS_PAYMENT_PURSE) { + Some(key) => *key, + None => return Ok(ExecutionResult::precondition_failure(Error::Deploy)), + }; + + let purse_balance_key = match tracking_copy + .borrow_mut() + .get_purse_balance_key(correlation_id, payment_purse_key) + { + Ok(key) => key, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error.into())); + } + }; + + match tracking_copy + .borrow_mut() + .get_purse_balance(correlation_id, purse_balance_key) + { + Ok(balance) => balance, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error.into())); + } + } + }; + + if let Some(forced_transfer) = payment_result.check_forced_transfer(payment_purse_balance) { + // Get rewards purse balance key + // payment_code_spec_6: system contract validity + let rewards_purse_balance_key: Key = { + // Get reward purse Key from proof of stake contract + // payment_code_spec_6: system contract validity + let rewards_purse_key: Key = + match proof_of_stake_contract.named_keys().get(POS_REWARDS_PURSE) { + Some(key) => *key, + None => { + return Ok(ExecutionResult::precondition_failure(Error::Deploy)); + } + }; + + match tracking_copy + .borrow_mut() + .get_purse_balance_key(correlation_id, rewards_purse_key) + { + Ok(key) => key, + Err(error) => { + return Ok(ExecutionResult::precondition_failure(error.into())); + } + } + }; + + let error = match forced_transfer { + ForcedTransferResult::InsufficientPayment => Error::InsufficientPayment, + ForcedTransferResult::PaymentFailure => payment_result.take_error().unwrap(), + }; + return Ok(ExecutionResult::new_payment_code_error( + error, + max_payment_cost, + account_main_purse_balance, + account_main_purse_balance_key, + rewards_purse_balance_key, + )); + } + + execution_result_builder.set_payment_execution_result(payment_result); + + let post_payment_tracking_copy = tracking_copy.borrow(); + let session_tracking_copy = Rc::new(RefCell::new(post_payment_tracking_copy.fork())); + + // session_code_spec_2: execute session code + let ( + session_module, + session_base_key, + mut session_named_keys, + session_package, + session_entry_point, + ) = match session_module { + GetModuleResult::Session { + module, + contract_package, + entry_point, + } => ( + module, + base_key, + account.named_keys().clone(), + contract_package, + entry_point, + ), + GetModuleResult::Contract { + module, + base_key, + contract, + contract_package, + entry_point, + } => ( + module, + base_key, + contract.named_keys().clone(), + contract_package, + entry_point, + ), + }; + + let session_args = match session.into_runtime_args() { + Ok(args) => args, + Err(e) => { + let exec_err: execution::Error = e.into(); + warn!("Unable to deserialize session arguments: {:?}", exec_err); + return Ok(ExecutionResult::precondition_failure(exec_err.into())); + } + }; + let session_result = { + // payment_code_spec_3_b_i: if (balance of PoS pay purse) >= (gas spent during + // payment code execution) * conv_rate, yes session + // session_code_spec_1: gas limit = ((balance of PoS payment purse) / conv_rate) + // - (gas spent during payment execution) + let session_gas_limit: Gas = Gas::from_motes(payment_purse_balance, CONV_RATE) + .unwrap_or_default() + - payment_result_cost; + let system_contract_cache = SystemContractCache::clone(&self.system_contract_cache); + + executor.exec( + session_module, + session_entry_point, + session_args, + session_base_key, + &account, + &mut session_named_keys, + authorization_keys.clone(), + blocktime, + deploy_hash, + session_gas_limit, + protocol_version, + correlation_id, + Rc::clone(&session_tracking_copy), + Phase::Session, + protocol_data, + system_contract_cache, + &session_package, + ) + }; + debug!("Session result: {:?}", session_result); + + let post_session_rc = if session_result.is_failure() { + // If session code fails we do not include its effects, + // so we start again from the post-payment state. + Rc::new(RefCell::new(post_payment_tracking_copy.fork())) + } else { + session_tracking_copy + }; + + // NOTE: session_code_spec_3: (do not include session execution effects in + // results) is enforced in execution_result_builder.build() + execution_result_builder.set_session_execution_result(session_result); + + // payment_code_spec_5: run finalize process + let (_, finalize_result): (Option<()>, ExecutionResult) = { + let post_session_tc = post_session_rc.borrow(); + let finalization_tc = Rc::new(RefCell::new(post_session_tc.fork())); + + let proof_of_stake_args = { + //((gas spent during payment code execution) + (gas spent during session code execution)) * conv_rate + let finalize_cost_motes: Motes = + Motes::from_gas(execution_result_builder.total_cost(), CONV_RATE) + .expect("motes overflow"); + const ARG_AMOUNT: &str = "amount"; + const ARG_ACCOUNT_KEY: &str = "account"; + runtime_args! { + ARG_AMOUNT => finalize_cost_motes.value(), + ARG_ACCOUNT_KEY => account_public_key, + } + }; + + // The PoS keys may have changed because of effects during payment and/or + // session, so we need to look them up again from the tracking copy + let proof_of_stake_contract = match finalization_tc + .borrow_mut() + .get_contract(correlation_id, proof_of_stake_hash) + { + Ok(info) => info, + Err(error) => return Ok(ExecutionResult::precondition_failure(error.into())), + }; + + let mut proof_of_stake_keys = proof_of_stake_contract.named_keys().to_owned(); + + let gas_limit = Gas::new(U512::from(std::u64::MAX)); + let system_contract_cache = SystemContractCache::clone(&self.system_contract_cache); + + executor.exec_system_contract( + DirectSystemContractCall::FinalizePayment, + proof_of_stake_module, + proof_of_stake_args, + &mut proof_of_stake_keys, + Default::default(), + Key::from(protocol_data.proof_of_stake()), + &system_account, + authorization_keys, + blocktime, + deploy_hash, + gas_limit, + protocol_version, + correlation_id, + finalization_tc, + Phase::FinalizePayment, + protocol_data, + system_contract_cache, + ) + }; + + execution_result_builder.set_finalize_execution_result(finalize_result); + + // We panic here to indicate that the builder was not used properly. + let ret = execution_result_builder + .build(tracking_copy.borrow().reader(), correlation_id) + .expect("ExecutionResultBuilder not initialized properly"); + + // NOTE: payment_code_spec_5_a is enforced in execution_result_builder.build() + // payment_code_spec_6: return properly combined set of transforms and + // appropriate error + Ok(ret) + } + + pub fn apply_effect( + &self, + correlation_id: CorrelationId, + protocol_version: ProtocolVersion, + pre_state_hash: Blake2bHash, + effects: AdditiveMap, + ) -> Result + where + Error: From, + { + match self.state.commit(correlation_id, pre_state_hash, effects)? { + CommitResult::Success { state_root, .. } => { + let bonded_validators = + self.get_bonded_validators(correlation_id, protocol_version, state_root)?; + Ok(CommitResult::Success { + state_root, + bonded_validators, + }) + } + commit_result => Ok(commit_result), + } + } + + /// Calculates bonded validators at `root_hash` state. + /// + /// Should only be called with a valid root hash after a successful call to + /// [`StateProvider::commit`]. Will panic if called with an invalid root hash. + fn get_bonded_validators( + &self, + correlation_id: CorrelationId, + protocol_version: ProtocolVersion, + root_hash: Blake2bHash, + ) -> Result, Error> + where + Error: From, + { + let protocol_data = match self.state.get_protocol_data(protocol_version)? { + Some(protocol_data) => protocol_data, + None => return Err(Error::InvalidProtocolVersion(protocol_version)), + }; + + let proof_of_stake_key = protocol_data.proof_of_stake().into(); + + let reader = match self.state.checkout(root_hash)? { + Some(reader) => reader, + None => panic!("get_bonded_validators called with an invalid root hash"), + }; + + let contract = match reader.read(correlation_id, &proof_of_stake_key)? { + Some(StoredValue::Contract(contract)) => contract, + _ => return Err(MissingSystemContract(PROOF_OF_STAKE.to_string())), + }; + + let bonded_validators = contract + .named_keys() + .keys() + .filter_map(|entry| utils::pos_validator_key_name_to_tuple(entry)) + .collect::>(); + + Ok(bonded_validators) + } +} diff --git a/node/src/contract_core/engine_state/op.rs b/node/src/contract_core/engine_state/op.rs new file mode 100644 index 0000000000..d618de1fbc --- /dev/null +++ b/node/src/contract_core/engine_state/op.rs @@ -0,0 +1,45 @@ +use std::{ + default::Default, + fmt::{self, Display, Formatter}, + ops::{Add, AddAssign}, +}; + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub enum Op { + Read, + Write, + Add, + NoOp, +} + +impl Add for Op { + type Output = Op; + + fn add(self, other: Op) -> Op { + match (self, other) { + (a, Op::NoOp) => a, + (Op::NoOp, b) => b, + (Op::Read, Op::Read) => Op::Read, + (Op::Add, Op::Add) => Op::Add, + _ => Op::Write, + } + } +} + +impl AddAssign for Op { + fn add_assign(&mut self, other: Self) { + *self = *self + other; + } +} + +impl Display for Op { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl Default for Op { + fn default() -> Self { + Op::NoOp + } +} diff --git a/node/src/contract_core/engine_state/query.rs b/node/src/contract_core/engine_state/query.rs new file mode 100644 index 0000000000..81c9b61c54 --- /dev/null +++ b/node/src/contract_core/engine_state/query.rs @@ -0,0 +1,52 @@ +use crate::contract_shared::{newtypes::Blake2bHash, stored_value::StoredValue}; +use types::Key; + +use crate::contract_core::tracking_copy::TrackingCopyQueryResult; + +pub enum QueryResult { + RootNotFound, + ValueNotFound(String), + CircularReference(String), + Success(StoredValue), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct QueryRequest { + state_hash: Blake2bHash, + key: Key, + path: Vec, +} + +impl QueryRequest { + pub fn new(state_hash: Blake2bHash, key: Key, path: Vec) -> Self { + QueryRequest { + state_hash, + key, + path, + } + } + + pub fn state_hash(&self) -> Blake2bHash { + self.state_hash + } + + pub fn key(&self) -> Key { + self.key + } + + pub fn path(&self) -> &[String] { + &self.path + } +} + +impl From for QueryResult { + fn from(tracking_copy_query_result: TrackingCopyQueryResult) -> Self { + match tracking_copy_query_result { + TrackingCopyQueryResult::ValueNotFound(message) => QueryResult::ValueNotFound(message), + TrackingCopyQueryResult::CircularReference(message) => { + QueryResult::CircularReference(message) + } + TrackingCopyQueryResult::Success(value) => QueryResult::Success(value), + } + } +} diff --git a/node/src/contract_core/engine_state/run_genesis_request.rs b/node/src/contract_core/engine_state/run_genesis_request.rs new file mode 100644 index 0000000000..26a5e8e6f4 --- /dev/null +++ b/node/src/contract_core/engine_state/run_genesis_request.rs @@ -0,0 +1,63 @@ +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; +use std::{convert::TryInto, iter}; + +use super::genesis::ExecConfig; +use crate::contract_shared::newtypes::{Blake2bHash, BLAKE2B_DIGEST_LENGTH}; +use types::ProtocolVersion; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RunGenesisRequest { + genesis_config_hash: Blake2bHash, + protocol_version: ProtocolVersion, + ee_config: ExecConfig, +} + +impl RunGenesisRequest { + pub fn new( + genesis_config_hash: Blake2bHash, + protocol_version: ProtocolVersion, + ee_config: ExecConfig, + ) -> RunGenesisRequest { + RunGenesisRequest { + genesis_config_hash, + protocol_version, + ee_config, + } + } + + pub fn genesis_config_hash(&self) -> Blake2bHash { + self.genesis_config_hash + } + + pub fn protocol_version(&self) -> ProtocolVersion { + self.protocol_version + } + + pub fn ee_config(&self) -> &ExecConfig { + &self.ee_config + } + + pub fn take_ee_config(self) -> ExecConfig { + self.ee_config + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> RunGenesisRequest { + let hash_bytes: [u8; BLAKE2B_DIGEST_LENGTH] = { + let bytes: Vec = iter::repeat(()) + .map(|_| rng.gen()) + .take(BLAKE2B_DIGEST_LENGTH) + .collect(); + bytes.as_slice().try_into().expect("should convert") + }; + + let protocol_version = ProtocolVersion::from_parts(rng.gen(), rng.gen(), rng.gen()); + let ee_config = rng.gen(); + + RunGenesisRequest::new(hash_bytes.into(), protocol_version, ee_config) + } +} diff --git a/node/src/contract_core/engine_state/system_contract_cache.rs b/node/src/contract_core/engine_state/system_contract_cache.rs new file mode 100644 index 0000000000..52fdd191c5 --- /dev/null +++ b/node/src/contract_core/engine_state/system_contract_cache.rs @@ -0,0 +1,253 @@ +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; + +use parity_wasm::elements::Module; + +use types::ContractHash; + +/// A cache of deserialized contracts. +#[derive(Clone, Default, Debug)] +pub struct SystemContractCache(Arc>>); + +impl SystemContractCache { + /// Returns `true` if the cache has a contract corresponding to `contract_hash`. + pub fn has(&self, contract_hash: ContractHash) -> bool { + let guarded_map = self.0.read().unwrap(); + guarded_map.contains_key(&contract_hash) + } + + /// Inserts `contract` into the cache under `contract_hash`. + /// + /// If the cache did not have this key present, `None` is returned. + /// + /// If the cache did have this key present, the value is updated, and the old value is returned. + pub fn insert(&self, contract_hash: ContractHash, module: Module) -> Option { + let mut guarded_map = self.0.write().unwrap(); + guarded_map.insert(contract_hash, module) + } + + /// Returns a clone of the contract corresponding to `contract_hash`. + pub fn get(&self, contract_hash: ContractHash) -> Option { + let guarded_map = self.0.read().unwrap(); + guarded_map.get(&contract_hash).cloned() + } +} + +#[cfg(test)] +mod tests { + use std::sync::Mutex; + + use lazy_static::lazy_static; + use parity_wasm::elements::{Module, ModuleNameSubsection, NameSection, Section}; + + use crate::contract_core::{ + engine_state::system_contract_cache::SystemContractCache, + execution::{AddressGenerator, AddressGeneratorBuilder}, + }; + use types::ContractHash; + + lazy_static! { + static ref ADDRESS_GENERATOR: Mutex = Mutex::new( + AddressGeneratorBuilder::new() + .seed_with(b"test_seed") + .build() + ); + } + + #[test] + fn should_insert_module() { + let reference = { + let mut address_generator = ADDRESS_GENERATOR.lock().unwrap(); + address_generator.create_address() + }; + let module = Module::default(); + + let cache = SystemContractCache::default(); + + let result = cache.insert(reference, module); + + assert!(result.is_none()) + } + + #[test] + fn should_has_false() { + let cache = SystemContractCache::default(); + let reference = { + let mut address_generator = ADDRESS_GENERATOR.lock().unwrap(); + address_generator.create_address() + }; + + assert!(!cache.has(reference)) + } + + #[test] + fn should_has_true() { + let cache = SystemContractCache::default(); + let reference = { + let mut address_generator = ADDRESS_GENERATOR.lock().unwrap(); + address_generator.create_address() + }; + let module = Module::default(); + + cache.insert(reference, module); + + assert!(cache.has(reference)) + } + + #[test] + fn should_has_true_normalized_has() { + let cache = SystemContractCache::default(); + let reference = { + let mut address_generator = ADDRESS_GENERATOR.lock().unwrap(); + address_generator.create_address() + }; + let module = Module::default(); + + cache.insert(reference, module); + + assert!(cache.has(reference)) + } + + #[test] + fn should_has_true_normalized_insert() { + let cache = SystemContractCache::default(); + let reference = { + let mut address_generator = ADDRESS_GENERATOR.lock().unwrap(); + address_generator.create_address() + }; + let module = Module::default(); + + cache.insert(reference, module); + + assert!(cache.has(reference)) + } + + #[test] + fn should_get_none() { + let reference = { + let mut address_generator = ADDRESS_GENERATOR.lock().unwrap(); + address_generator.create_address() + }; + let cache = SystemContractCache::default(); + + let result = cache.get(reference); + + assert!(result.is_none()) + } + + #[test] + fn should_get_module() { + let cache = SystemContractCache::default(); + let reference = { + let mut address_generator = ADDRESS_GENERATOR.lock().unwrap(); + address_generator.create_address() + }; + let module = Module::default(); + + cache.insert(reference, module.clone()); + + let result = cache.get(reference); + + assert_eq!(result, Some(module)) + } + + #[test] + fn should_get_module_normalized_get() { + let cache = SystemContractCache::default(); + let reference = { + let mut address_generator = ADDRESS_GENERATOR.lock().unwrap(); + address_generator.create_address() + }; + let module = Module::default(); + + cache.insert(reference, module.clone()); + + let result = cache.get(reference); + + assert_eq!(result, Some(module.clone())); + + let result = cache.get(reference); + + assert_eq!(result, Some(module)) + } + + #[test] + fn should_get_module_normalized_insert() { + let cache = SystemContractCache::default(); + let reference: ContractHash = { + let mut address_generator = ADDRESS_GENERATOR.lock().unwrap(); + address_generator.create_address() + }; + let module = Module::default(); + + cache.insert(reference, module.clone()); + + let result = cache.get(reference); + + assert_eq!(result, Some(module.clone())); + + let result = cache.get(reference); + + assert_eq!(result, Some(module)) + } + + #[test] + fn should_update_module() { + let cache = SystemContractCache::default(); + let reference = { + let mut address_generator = ADDRESS_GENERATOR.lock().unwrap(); + address_generator.create_address() + }; + let initial_module = Module::default(); + let updated_module = { + let section = NameSection::new(Some(ModuleNameSubsection::new("a_mod")), None, None); + let sections = vec![Section::Name(section)]; + Module::new(sections) + }; + + assert_ne!(initial_module, updated_module); + + let result = cache.insert(reference, initial_module.clone()); + + assert!(result.is_none()); + + let result = cache.insert(reference, updated_module.clone()); + + assert_eq!(result, Some(initial_module)); + + let result = cache.get(reference); + + assert_eq!(result, Some(updated_module)) + } + + #[test] + fn should_update_module_normalized() { + let cache = SystemContractCache::default(); + let reference = { + let mut address_generator = ADDRESS_GENERATOR.lock().unwrap(); + address_generator.create_address() + }; + let initial_module = Module::default(); + let updated_module = { + let section = NameSection::new(Some(ModuleNameSubsection::new("a_mod")), None, None); + let sections = vec![Section::Name(section)]; + Module::new(sections) + }; + + assert_ne!(initial_module, updated_module); + + let result = cache.insert(reference, initial_module.clone()); + + assert!(result.is_none()); + + let result = cache.insert(reference, updated_module.clone()); + + assert_eq!(result, Some(initial_module)); + + let result = cache.get(reference); + + assert_eq!(result, Some(updated_module)) + } +} diff --git a/node/src/contract_core/engine_state/transfer.rs b/node/src/contract_core/engine_state/transfer.rs new file mode 100644 index 0000000000..8ebad021a0 --- /dev/null +++ b/node/src/contract_core/engine_state/transfer.rs @@ -0,0 +1,289 @@ +use crate::contract_shared::{ + account::Account, newtypes::CorrelationId, stored_value::StoredValue, +}; +use crate::contract_storage::global_state::StateReader; +use std::{cell::RefCell, rc::Rc}; +use types::{account::AccountHash, AccessRights, ApiError, Key, RuntimeArgs, URef, U512}; + +use crate::contract_core::{ + engine_state::Error, + execution::Error as ExecError, + tracking_copy::{TrackingCopy, TrackingCopyExt}, +}; +use crate::contract_shared; + +const SOURCE: &str = "source"; +const TARGET: &str = "target"; +const AMOUNT: &str = "amount"; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum TransferTargetMode { + Unknown, + PurseExists(URef), + CreateAccount(AccountHash), +} + +pub struct TransferRuntimeArgsBuilder { + inner: RuntimeArgs, + transfer_target_mode: TransferTargetMode, +} + +impl TransferRuntimeArgsBuilder { + pub fn new(imputed_runtime_args: RuntimeArgs) -> TransferRuntimeArgsBuilder { + TransferRuntimeArgsBuilder { + inner: imputed_runtime_args, + transfer_target_mode: TransferTargetMode::Unknown, + } + } + + fn purse_exists( + &self, + uref: URef, + correlation_id: CorrelationId, + tracking_copy: Rc>>, + ) -> bool + where + R: StateReader, + R::Error: Into, + { + // it is a URef but is it a purse URef? + tracking_copy + .borrow_mut() + .get_purse_balance_key(correlation_id, uref.into()) + .is_ok() + } + + fn resolve_source_uref( + &self, + account: &Account, + correlation_id: CorrelationId, + tracking_copy: Rc>>, + ) -> Result + where + R: StateReader, + R::Error: Into, + { + let imputed_runtime_args = &self.inner; + let arg_name = SOURCE; + match imputed_runtime_args.get(arg_name) { + Some(cl_value) if *cl_value.cl_type() == types::CLType::URef => { + let uref: URef = match cl_value.clone().into_t() { + Ok(uref) => uref, + Err(error) => { + return Err(Error::Exec(ExecError::Revert(error.into()))); + } + }; + if account.main_purse().addr() == uref.addr() { + return Ok(uref); + } + + let normalized_uref = Key::URef(uref).normalize(); + let maybe_named_key = account + .named_keys() + .values() + .find(|&named_key| named_key.normalize() == normalized_uref); + match maybe_named_key { + Some(Key::URef(found_uref)) => { + if found_uref.is_writeable() { + // it is a URef and caller has access but is it a purse URef? + if !self.purse_exists( + found_uref.to_owned(), + correlation_id, + tracking_copy, + ) { + return Err(Error::Exec(ExecError::Revert(ApiError::InvalidPurse))); + } + + Ok(uref) + } else { + Err(Error::Exec(ExecError::InvalidAccess { + required: AccessRights::WRITE, + })) + } + } + Some(key) => Err(Error::Exec(ExecError::TypeMismatch( + contract_shared::TypeMismatch::new( + "Key::URef".to_string(), + key.type_string(), + ), + ))), + None => Err(Error::Exec(ExecError::ForgedReference(uref))), + } + } + Some(_) => Err(Error::Exec(ExecError::Revert(ApiError::InvalidArgument))), + None => Ok(account.main_purse()), // if no source purse passed use account main purse + } + } + + fn resolve_transfer_target_mode( + &self, + correlation_id: CorrelationId, + tracking_copy: Rc>>, + ) -> Result + where + R: StateReader, + R::Error: Into, + { + let imputed_runtime_args = &self.inner; + let arg_name = TARGET; + match imputed_runtime_args.get(arg_name) { + Some(cl_value) if *cl_value.cl_type() == types::CLType::URef => { + let uref: URef = match cl_value.clone().into_t() { + Ok(uref) => uref, + Err(error) => { + return Err(Error::Exec(ExecError::Revert(error.into()))); + } + }; + + if !self.purse_exists(uref, correlation_id, tracking_copy) { + return Err(Error::Exec(ExecError::Revert(ApiError::InvalidPurse))); + } + + Ok(TransferTargetMode::PurseExists(uref)) + } + Some(cl_value) + if *cl_value.cl_type() + == types::CLType::FixedList(Box::new(types::CLType::U8), 32) => + { + let account_key: Key = { + let hash = match cl_value.clone().into_t() { + Ok(hash) => hash, + Err(error) => { + return Err(Error::Exec(ExecError::Revert(error.into()))); + } + }; + Key::Account(hash) + }; + match account_key.into_account() { + Some(public_key) => { + match tracking_copy + .borrow_mut() + .read_account(correlation_id, public_key) + { + Ok(account) => { + Ok(TransferTargetMode::PurseExists(account.main_purse())) + } + Err(_) => Ok(TransferTargetMode::CreateAccount(public_key)), + } + } + None => Err(Error::Exec(ExecError::Revert(ApiError::Transfer))), + } + } + Some(cl_value) if *cl_value.cl_type() == types::CLType::Key => { + let account_key: Key = match cl_value.clone().into_t() { + Ok(key) => key, + Err(error) => { + return Err(Error::Exec(ExecError::Revert(error.into()))); + } + }; + match account_key.into_account() { + Some(public_key) => { + match tracking_copy + .borrow_mut() + .read_account(correlation_id, public_key) + { + Ok(account) => { + Ok(TransferTargetMode::PurseExists(account.main_purse())) + } + Err(_) => Ok(TransferTargetMode::CreateAccount(public_key)), + } + } + None => Err(Error::Exec(ExecError::Revert(ApiError::Transfer))), + } + } + Some(_) => Err(Error::Exec(ExecError::Revert(ApiError::InvalidArgument))), + None => Err(Error::Exec(ExecError::Revert(ApiError::MissingArgument))), + } + } + + fn resolve_amount(&self) -> Result { + let imputed_runtime_args = &self.inner; + match imputed_runtime_args.get(AMOUNT) { + Some(amount_value) if *amount_value.cl_type() == types::CLType::U512 => { + match amount_value.clone().into_t::() { + Ok(amount) => { + if amount == U512::zero() { + Err(Error::Exec(ExecError::Revert(ApiError::Transfer))) + } else { + Ok(amount) + } + } + Err(error) => Err(Error::Exec(ExecError::Revert(error.into()))), + } + } + Some(amount_value) if *amount_value.cl_type() == types::CLType::U64 => { + match amount_value.clone().into_t::() { + Ok(amount) => match amount { + 0 => Err(Error::Exec(ExecError::Revert(ApiError::Transfer))), + _ => Ok(U512::from(amount)), + }, + Err(error) => Err(Error::Exec(ExecError::Revert(error.into()))), + } + } + Some(_) => Err(Error::Exec(ExecError::Revert(ApiError::InvalidArgument))), + None => Err(Error::Exec(ExecError::Revert(ApiError::MissingArgument))), + } + } + + pub fn transfer_target_mode( + &mut self, + correlation_id: CorrelationId, + tracking_copy: Rc>>, + ) -> Result + where + R: StateReader, + R::Error: Into, + { + let mode = self.transfer_target_mode; + if mode != TransferTargetMode::Unknown { + return Ok(mode); + } + match self.resolve_transfer_target_mode(correlation_id, tracking_copy) { + Ok(mode) => { + self.transfer_target_mode = mode; + Ok(mode) + } + Err(error) => Err(error), + } + } + + pub fn build( + self, + account: &Account, + correlation_id: CorrelationId, + tracking_copy: Rc>>, + ) -> Result + where + R: StateReader, + R::Error: Into, + { + let target_uref = + match self.resolve_transfer_target_mode(correlation_id, Rc::clone(&tracking_copy))? { + TransferTargetMode::PurseExists(uref) => uref, + _ => { + return Err(Error::Exec(ExecError::Revert(ApiError::Transfer))); + } + }; + + let source_uref = + self.resolve_source_uref(account, correlation_id, Rc::clone(&tracking_copy))?; + + if source_uref.addr() == target_uref.addr() { + return Err(ExecError::Revert(ApiError::InvalidPurse).into()); + } + + let amount = self.resolve_amount()?; + + let runtime_args = { + let mut runtime_args = RuntimeArgs::new(); + + runtime_args.insert(SOURCE, source_uref); + runtime_args.insert(TARGET, target_uref); + runtime_args.insert(AMOUNT, amount); + + runtime_args + }; + + Ok(runtime_args) + } +} diff --git a/node/src/contract_core/engine_state/upgrade.rs b/node/src/contract_core/engine_state/upgrade.rs new file mode 100644 index 0000000000..a760300362 --- /dev/null +++ b/node/src/contract_core/engine_state/upgrade.rs @@ -0,0 +1,116 @@ +use std::fmt; + +use crate::contract_shared::wasm_costs::WasmCosts; +use crate::contract_shared::{newtypes::Blake2bHash, TypeMismatch}; +use crate::contract_storage::global_state::CommitResult; +use types::{bytesrepr, Key, ProtocolVersion}; + +use crate::contract_core::engine_state::execution_effect::ExecutionEffect; + +pub type ActivationPoint = u64; + +pub enum UpgradeResult { + RootNotFound, + KeyNotFound(Key), + TypeMismatch(TypeMismatch), + Serialization(bytesrepr::Error), + Success { + post_state_hash: Blake2bHash, + effect: ExecutionEffect, + }, +} + +impl fmt::Display for UpgradeResult { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + UpgradeResult::RootNotFound => write!(f, "Root not found"), + UpgradeResult::KeyNotFound(key) => write!(f, "Key not found: {}", key), + UpgradeResult::TypeMismatch(type_mismatch) => { + write!(f, "Type mismatch: {:?}", type_mismatch) + } + UpgradeResult::Serialization(error) => write!(f, "Serialization error: {:?}", error), + UpgradeResult::Success { + post_state_hash, + effect, + } => write!(f, "Success: {} {:?}", post_state_hash, effect), + } + } +} + +impl UpgradeResult { + pub fn from_commit_result(commit_result: CommitResult, effect: ExecutionEffect) -> Self { + match commit_result { + CommitResult::RootNotFound => UpgradeResult::RootNotFound, + CommitResult::KeyNotFound(key) => UpgradeResult::KeyNotFound(key), + CommitResult::TypeMismatch(type_mismatch) => UpgradeResult::TypeMismatch(type_mismatch), + CommitResult::Serialization(error) => UpgradeResult::Serialization(error), + CommitResult::Success { state_root, .. } => UpgradeResult::Success { + post_state_hash: state_root, + effect, + }, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UpgradeConfig { + pre_state_hash: Blake2bHash, + current_protocol_version: ProtocolVersion, + new_protocol_version: ProtocolVersion, + upgrade_installer_args: Option>, + upgrade_installer_bytes: Option>, + wasm_costs: Option, + activation_point: Option, +} + +impl UpgradeConfig { + pub fn new( + pre_state_hash: Blake2bHash, + current_protocol_version: ProtocolVersion, + new_protocol_version: ProtocolVersion, + upgrade_installer_args: Option>, + upgrade_installer_bytes: Option>, + wasm_costs: Option, + activation_point: Option, + ) -> Self { + UpgradeConfig { + pre_state_hash, + current_protocol_version, + new_protocol_version, + upgrade_installer_args, + upgrade_installer_bytes, + wasm_costs, + activation_point, + } + } + + pub fn pre_state_hash(&self) -> Blake2bHash { + self.pre_state_hash + } + + pub fn current_protocol_version(&self) -> ProtocolVersion { + self.current_protocol_version + } + + pub fn new_protocol_version(&self) -> ProtocolVersion { + self.new_protocol_version + } + + pub fn upgrade_installer_args(&self) -> Option<&[u8]> { + let args = self.upgrade_installer_args.as_ref()?; + Some(args.as_slice()) + } + + pub fn upgrade_installer_bytes(&self) -> Option<&[u8]> { + let bytes = self.upgrade_installer_bytes.as_ref()?; + Some(bytes.as_slice()) + } + + pub fn wasm_costs(&self) -> Option { + self.wasm_costs + } + + pub fn activation_point(&self) -> Option { + self.activation_point + } +} diff --git a/node/src/contract_core/engine_state/utils.rs b/node/src/contract_core/engine_state/utils.rs new file mode 100644 index 0000000000..577e85e592 --- /dev/null +++ b/node/src/contract_core/engine_state/utils.rs @@ -0,0 +1,86 @@ +use types::{account::AccountHash, U512}; + +/// In PoS, the validators are stored under named keys with names formatted as +/// "v__". This function attempts to parse such a string +/// back into the `AccountHash` and bond amount. +pub fn pos_validator_key_name_to_tuple(pos_key_name: &str) -> Option<(AccountHash, U512)> { + let mut split_bond = pos_key_name.split('_'); // expected format is "v_{account_hash}_{bond}". + if Some("v") != split_bond.next() { + None + } else { + let hex_key: &str = split_bond.next()?; + if hex_key.len() != 64 { + return None; + } + let mut key_bytes = [0u8; 32]; + let _bytes_written = base16::decode_slice(hex_key, &mut key_bytes).ok()?; + debug_assert!(_bytes_written == key_bytes.len()); + let pub_key = AccountHash::new(key_bytes); + let balance = split_bond.next().and_then(|b| { + if b.is_empty() { + None + } else { + U512::from_dec_str(b).ok() + } + })?; + Some((pub_key, balance)) + } +} + +#[cfg(test)] +mod tests { + use hex_fmt::HexFmt; + + use types::{account::AccountHash, U512}; + + use super::pos_validator_key_name_to_tuple; + + #[test] + fn should_parse_string_to_validator_tuple() { + let account_hash = AccountHash::new([1u8; 32]); + let stake = U512::from(100); + let named_key_name = format!("v_{}_{}", HexFmt(&account_hash.as_bytes()), stake); + + let parsed = pos_validator_key_name_to_tuple(&named_key_name); + assert!(parsed.is_some()); + let (parsed_account_hash, parsed_stake) = parsed.unwrap(); + assert_eq!(parsed_account_hash, account_hash); + assert_eq!(parsed_stake, stake); + } + + #[test] + fn should_not_parse_string_to_validator_tuple() { + let account_hash = AccountHash::new([1u8; 32]); + let stake = U512::from(100); + + let bad_prefix = format!("a_{}_{}", HexFmt(&account_hash.as_bytes()), stake); + assert!(pos_validator_key_name_to_tuple(&bad_prefix).is_none()); + + let no_prefix = format!("_{}_{}", HexFmt(&account_hash.as_bytes()), stake); + assert!(pos_validator_key_name_to_tuple(&no_prefix).is_none()); + + let short_key = format!("v_{}_{}", HexFmt(&[1u8; 31]), stake); + assert!(pos_validator_key_name_to_tuple(&short_key).is_none()); + + let long_key = format!("v_{}00_{}", HexFmt(&account_hash.as_bytes()), stake); + assert!(pos_validator_key_name_to_tuple(&long_key).is_none()); + + let bad_key = format!("v_{}0g_{}", HexFmt(&[1u8; 31]), stake); + assert!(pos_validator_key_name_to_tuple(&bad_key).is_none()); + + let no_key = format!("v__{}", stake); + assert!(pos_validator_key_name_to_tuple(&no_key).is_none()); + + let no_key = format!("v_{}", stake); + assert!(pos_validator_key_name_to_tuple(&no_key).is_none()); + + let bad_stake = format!("v_{}_a", HexFmt(&account_hash.as_bytes())); + assert!(pos_validator_key_name_to_tuple(&bad_stake).is_none()); + + let no_stake = format!("v_{}_", HexFmt(&account_hash.as_bytes())); + assert!(pos_validator_key_name_to_tuple(&no_stake).is_none()); + + let no_stake = format!("v_{}", HexFmt(&account_hash.as_bytes())); + assert!(pos_validator_key_name_to_tuple(&no_stake).is_none()); + } +} diff --git a/node/src/contract_core/execution/address_generator.rs b/node/src/contract_core/execution/address_generator.rs new file mode 100644 index 0000000000..2df79e8ee5 --- /dev/null +++ b/node/src/contract_core/execution/address_generator.rs @@ -0,0 +1,106 @@ +use blake2::{ + digest::{Input, VariableOutput}, + VarBlake2b, +}; +use rand::{RngCore, SeedableRng}; +use rand_chacha::ChaChaRng; + +use types::Phase; + +use crate::contract_core::{Address, ADDRESS_LENGTH}; + +const SEED_LENGTH: usize = 32; + +/// An [`AddressGenerator`] generates [`URef`](types::URef) addresses. +pub struct AddressGenerator(ChaChaRng); + +impl AddressGenerator { + /// Creates an [`AddressGenerator`] from a 32-byte hash digest and [`Phase`]. + pub fn new(hash: &[u8], phase: Phase) -> AddressGenerator { + AddressGeneratorBuilder::new() + .seed_with(&hash) + .seed_with(&[phase as u8]) + .build() + } + + pub fn create_address(&mut self) -> Address { + let mut buff = [0u8; ADDRESS_LENGTH]; + self.0.fill_bytes(&mut buff); + buff + } +} + +/// A builder for [`AddressGenerator`]. +#[derive(Default)] +pub struct AddressGeneratorBuilder { + data: Vec, +} + +impl AddressGeneratorBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn seed_with(mut self, bytes: &[u8]) -> Self { + self.data.extend(bytes); + self + } + + pub fn build(self) -> AddressGenerator { + let mut seed: [u8; SEED_LENGTH] = [0u8; SEED_LENGTH]; + let mut hasher = VarBlake2b::new(SEED_LENGTH).unwrap(); + hasher.input(self.data); + hasher.variable_result(|hash| seed.clone_from_slice(hash)); + AddressGenerator(ChaChaRng::from_seed(seed)) + } +} + +#[cfg(test)] +mod tests { + use types::Phase; + + use super::AddressGenerator; + + const DEPLOY_HASH_1: [u8; 32] = [1u8; 32]; + const DEPLOY_HASH_2: [u8; 32] = [2u8; 32]; + + #[test] + fn should_generate_different_numbers_for_different_seeds() { + let mut ag_a = AddressGenerator::new(&DEPLOY_HASH_1, Phase::Session); + let mut ag_b = AddressGenerator::new(&DEPLOY_HASH_2, Phase::Session); + let random_a = ag_a.create_address(); + let random_b = ag_b.create_address(); + + assert_ne!(random_a, random_b) + } + + #[test] + fn should_generate_same_numbers_for_same_seed() { + let mut ag_a = AddressGenerator::new(&DEPLOY_HASH_1, Phase::Session); + let mut ag_b = AddressGenerator::new(&DEPLOY_HASH_1, Phase::Session); + let random_a = ag_a.create_address(); + let random_b = ag_b.create_address(); + + assert_eq!(random_a, random_b) + } + + #[test] + fn should_not_generate_same_numbers_for_different_phase() { + let mut ag_a = AddressGenerator::new(&DEPLOY_HASH_1, Phase::Payment); + let mut ag_b = AddressGenerator::new(&DEPLOY_HASH_1, Phase::Session); + let mut ag_c = AddressGenerator::new(&DEPLOY_HASH_1, Phase::FinalizePayment); + let random_a = ag_a.create_address(); + let random_b = ag_b.create_address(); + let random_c = ag_c.create_address(); + + assert_ne!( + random_a, random_b, + "different phase should have different output" + ); + + assert_ne!( + random_a, random_c, + "different phase should have different output" + ); + } +} diff --git a/node/src/contract_core/execution/error.rs b/node/src/contract_core/execution/error.rs new file mode 100644 index 0000000000..8833c62d03 --- /dev/null +++ b/node/src/contract_core/execution/error.rs @@ -0,0 +1,182 @@ +use failure::Fail; +use parity_wasm::elements; + +use crate::contract_shared::{wasm_prep, TypeMismatch}; +use types::{ + account::{AddKeyFailure, RemoveKeyFailure, SetThresholdFailure, UpdateKeyFailure}, + bytesrepr, system_contract_errors, AccessRights, ApiError, CLType, CLValueError, + ContractPackageHash, ContractVersionKey, Key, URef, +}; + +use crate::contract_core::resolvers::error::ResolverError; +use crate::contract_storage; + +#[derive(Fail, Debug, Clone)] +pub enum Error { + #[fail(display = "Interpreter error: {}", _0)] + Interpreter(String), + #[fail(display = "Storage error: {}", _0)] + Storage(contract_storage::error::Error), + #[fail(display = "Serialization error: {}", _0)] + BytesRepr(bytesrepr::Error), + #[fail(display = "Named key {} not found", _0)] + NamedKeyNotFound(String), + #[fail(display = "Key {} not found", _0)] + KeyNotFound(Key), + #[fail(display = "Account {:?} not found", _0)] + AccountNotFound(Key), + #[fail(display = "{}", _0)] + TypeMismatch(TypeMismatch), + #[fail(display = "Invalid access rights: {}", required)] + InvalidAccess { required: AccessRights }, + #[fail(display = "Forged reference: {}", _0)] + ForgedReference(URef), + #[fail(display = "URef not found: {}", _0)] + URefNotFound(String), + #[fail(display = "Function not found: {}", _0)] + FunctionNotFound(String), + #[fail(display = "{}", _0)] + ParityWasm(elements::Error), + #[fail(display = "Out of gas error")] + GasLimit, + #[fail(display = "Return")] + Ret(Vec), + #[fail(display = "{}", _0)] + Rng(String), + #[fail(display = "Resolver error: {}", _0)] + Resolver(ResolverError), + /// Reverts execution with a provided status + #[fail(display = "{}", _0)] + Revert(ApiError), + #[fail(display = "{}", _0)] + AddKeyFailure(AddKeyFailure), + #[fail(display = "{}", _0)] + RemoveKeyFailure(RemoveKeyFailure), + #[fail(display = "{}", _0)] + UpdateKeyFailure(UpdateKeyFailure), + #[fail(display = "{}", _0)] + SetThresholdFailure(SetThresholdFailure), + #[fail(display = "{}", _0)] + SystemContract(system_contract_errors::Error), + #[fail(display = "Deployment authorization failure")] + DeploymentAuthorizationFailure, + #[fail(display = "Expected return value")] + ExpectedReturnValue, + #[fail(display = "Unexpected return value")] + UnexpectedReturnValue, + #[fail(display = "Invalid context")] + InvalidContext, + #[fail( + display = "Incompatible protocol major version. Expected version {} but actual version is {}", + expected, actual + )] + IncompatibleProtocolMajorVersion { expected: u32, actual: u32 }, + #[fail(display = "{}", _0)] + CLValue(CLValueError), + #[fail(display = "Host buffer is empty")] + HostBufferEmpty, + #[fail(display = "Unsupported WASM start")] + UnsupportedWasmStart, + #[fail(display = "No active contract versions for contract package")] + NoActiveContractVersions(ContractPackageHash), + #[fail(display = "Invalid contract version: {}", _0)] + InvalidContractVersion(ContractVersionKey), + #[fail(display = "No such method: {}", _0)] + NoSuchMethod(String), + #[fail(display = "Wasm preprocessing error: {}", _0)] + WasmPreprocessing(wasm_prep::PreprocessingError), + #[fail( + display = "Unexpected Key length. Expected length {} but actual length is {}", + expected, actual + )] + InvalidKeyLength { expected: usize, actual: usize }, +} + +impl From for Error { + fn from(error: wasm_prep::PreprocessingError) -> Self { + Error::WasmPreprocessing(error) + } +} + +impl Error { + pub fn type_mismatch(expected: CLType, found: CLType) -> Error { + Error::TypeMismatch(TypeMismatch { + expected: format!("{:?}", expected), + found: format!("{:?}", found), + }) + } +} + +impl wasmi::HostError for Error {} + +impl From for Error { + fn from(error: wasmi::Error) -> Self { + match error + .as_host_error() + .and_then(|host_error| host_error.downcast_ref::()) + { + Some(error) => error.clone(), + None => Error::Interpreter(error.into()), + } + } +} + +impl From for Error { + fn from(e: contract_storage::error::Error) -> Self { + Error::Storage(e) + } +} + +impl From for Error { + fn from(e: bytesrepr::Error) -> Self { + Error::BytesRepr(e) + } +} + +impl From for Error { + fn from(e: elements::Error) -> Self { + Error::ParityWasm(e) + } +} + +impl From for Error { + fn from(err: ResolverError) -> Self { + Error::Resolver(err) + } +} + +impl From for Error { + fn from(err: AddKeyFailure) -> Self { + Error::AddKeyFailure(err) + } +} + +impl From for Error { + fn from(err: RemoveKeyFailure) -> Self { + Error::RemoveKeyFailure(err) + } +} + +impl From for Error { + fn from(err: UpdateKeyFailure) -> Self { + Error::UpdateKeyFailure(err) + } +} + +impl From for Error { + fn from(err: SetThresholdFailure) -> Self { + Error::SetThresholdFailure(err) + } +} + +impl From for Error { + fn from(error: system_contract_errors::Error) -> Self { + Error::SystemContract(error) + } +} + +impl From for Error { + fn from(e: CLValueError) -> Self { + Error::CLValue(e) + } +} diff --git a/node/src/contract_core/execution/executor.rs b/node/src/contract_core/execution/executor.rs new file mode 100644 index 0000000000..188b9885aa --- /dev/null +++ b/node/src/contract_core/execution/executor.rs @@ -0,0 +1,622 @@ +use std::{cell::RefCell, collections::BTreeSet, rc::Rc}; + +use parity_wasm::elements::Module; +use tracing::warn; +use wasmi::ModuleRef; + +use crate::contract_shared::{ + account::Account, gas::Gas, newtypes::CorrelationId, stored_value::StoredValue, +}; +use crate::contract_storage::{global_state::StateReader, protocol_data::ProtocolData}; +use types::{ + account::AccountHash, bytesrepr::FromBytes, contracts::NamedKeys, AccessRights, BlockTime, + CLTyped, CLValue, ContractPackage, EntryPoint, EntryPointType, Key, Phase, ProtocolVersion, + RuntimeArgs, +}; + +use crate::contract_core::{ + engine_state::{ + execution_effect::ExecutionEffect, execution_result::ExecutionResult, + system_contract_cache::SystemContractCache, EngineConfig, + }, + execution::{address_generator::AddressGenerator, Error}, + runtime::{ + extract_access_rights_from_keys, extract_access_rights_from_urefs, instance_and_memory, + Runtime, + }, + runtime_context::{self, RuntimeContext}, + tracking_copy::TrackingCopy, + Address, +}; +use std::collections::{HashMap, HashSet}; + +macro_rules! on_fail_charge { + ($fn:expr) => { + match $fn { + Ok(res) => res, + Err(e) => { + let exec_err: Error = e.into(); + warn!("Execution failed: {:?}", exec_err); + return ExecutionResult::precondition_failure(exec_err.into()); + } + } + }; + ($fn:expr, $cost:expr) => { + match $fn { + Ok(res) => res, + Err(e) => { + let exec_err: Error = e.into(); + warn!("Execution failed: {:?}", exec_err); + return ExecutionResult::Failure { + error: exec_err.into(), + effect: Default::default(), + cost: $cost, + }; + } + } + }; + ($fn:expr, $cost:expr, $effect:expr) => { + match $fn { + Ok(res) => res, + Err(e) => { + let exec_err: Error = e.into(); + warn!("Execution failed: {:?}", exec_err); + return ExecutionResult::Failure { + error: exec_err.into(), + effect: $effect, + cost: $cost, + }; + } + } + }; +} + +pub struct Executor { + config: EngineConfig, +} + +#[allow(clippy::too_many_arguments)] +impl Executor { + pub fn new(config: EngineConfig) -> Self { + Executor { config } + } + + pub fn config(&self) -> EngineConfig { + self.config + } + + pub fn exec( + &self, + module: Module, + entry_point: EntryPoint, + args: RuntimeArgs, + base_key: Key, + account: &Account, + named_keys: &mut NamedKeys, + authorization_keys: BTreeSet, + blocktime: BlockTime, + deploy_hash: [u8; 32], + gas_limit: Gas, + protocol_version: ProtocolVersion, + correlation_id: CorrelationId, + tracking_copy: Rc>>, + phase: Phase, + protocol_data: ProtocolData, + system_contract_cache: SystemContractCache, + contract_package: &ContractPackage, + ) -> ExecutionResult + where + R: StateReader, + R::Error: Into, + { + let entry_point_name = entry_point.name(); + let entry_point_type = entry_point.entry_point_type(); + let entry_point_access = entry_point.access(); + + let (instance, memory) = + on_fail_charge!(instance_and_memory(module.clone(), protocol_version)); + + let access_rights = { + let keys: Vec = named_keys.values().cloned().collect(); + extract_access_rights_from_keys(keys) + }; + + let hash_address_generator = { + let generator = AddressGenerator::new(&deploy_hash, phase); + Rc::new(RefCell::new(generator)) + }; + let uref_address_generator = { + let generator = AddressGenerator::new(&deploy_hash, phase); + Rc::new(RefCell::new(generator)) + }; + let gas_counter: Gas = Gas::default(); + + // Snapshot of effects before execution, so in case of error + // only nonce update can be returned. + let effects_snapshot = tracking_copy.borrow().effect(); + + let context = RuntimeContext::new( + tracking_copy, + entry_point_type, + named_keys, + access_rights, + args.clone(), + authorization_keys, + &account, + base_key, + blocktime, + deploy_hash, + gas_limit, + gas_counter, + hash_address_generator, + uref_address_generator, + protocol_version, + correlation_id, + phase, + protocol_data, + ); + + let mut runtime = Runtime::new(self.config, system_contract_cache, memory, module, context); + + let accounts_access_rights = { + let keys: Vec = account.named_keys().values().cloned().collect(); + extract_access_rights_from_keys(keys) + }; + + on_fail_charge!(runtime_context::validate_entry_point_access_with( + &contract_package, + entry_point_access, + |uref| runtime_context::uref_has_access_rights(uref, &accounts_access_rights) + )); + + if !self.config.use_system_contracts() { + if runtime.is_mint(base_key) { + match runtime.call_host_mint( + protocol_version, + entry_point.name(), + &mut runtime.context().named_keys().to_owned(), + &args, + Default::default(), + ) { + Ok(_value) => { + return ExecutionResult::Success { + effect: runtime.context().effect(), + cost: runtime.context().gas_counter(), + }; + } + Err(error) => { + return ExecutionResult::Failure { + error: error.into(), + effect: effects_snapshot, + cost: runtime.context().gas_counter(), + }; + } + } + } else if runtime.is_proof_of_stake(base_key) { + match runtime.call_host_proof_of_stake( + protocol_version, + entry_point.name(), + &mut runtime.context().named_keys().to_owned(), + &args, + Default::default(), + ) { + Ok(_value) => { + return ExecutionResult::Success { + effect: runtime.context().effect(), + cost: runtime.context().gas_counter(), + }; + } + Err(error) => { + return ExecutionResult::Failure { + error: error.into(), + effect: effects_snapshot, + cost: runtime.context().gas_counter(), + }; + } + } + } + } + + on_fail_charge!( + instance.invoke_export(entry_point_name, &[], &mut runtime), + runtime.context().gas_counter(), + effects_snapshot + ); + + ExecutionResult::Success { + effect: runtime.context().effect(), + cost: runtime.context().gas_counter(), + } + } + + pub fn exec_system_contract( + &self, + direct_system_contract_call: DirectSystemContractCall, + module: Module, + runtime_args: RuntimeArgs, + named_keys: &mut NamedKeys, + extra_keys: &[Key], + base_key: Key, + account: &Account, + authorization_keys: BTreeSet, + blocktime: BlockTime, + deploy_hash: [u8; 32], + gas_limit: Gas, + protocol_version: ProtocolVersion, + correlation_id: CorrelationId, + tracking_copy: Rc>>, + phase: Phase, + protocol_data: ProtocolData, + system_contract_cache: SystemContractCache, + ) -> (Option, ExecutionResult) + where + R: StateReader, + R::Error: Into, + T: FromBytes + CLTyped, + { + match direct_system_contract_call { + DirectSystemContractCall::FinalizePayment => { + if protocol_data.proof_of_stake() != base_key.into_seed() { + panic!( + "{} should only be called with the proof of stake contract", + direct_system_contract_call.entry_point_name() + ); + } + } + DirectSystemContractCall::CreatePurse | DirectSystemContractCall::Transfer => { + if protocol_data.mint() != base_key.into_seed() { + panic!( + "{} should only be called with the mint contract", + direct_system_contract_call.entry_point_name() + ); + } + } + } + + let hash_address_generator = { + let generator = AddressGenerator::new(&deploy_hash, phase); + Rc::new(RefCell::new(generator)) + }; + let uref_address_generator = { + let generator = AddressGenerator::new(&deploy_hash, phase); + Rc::new(RefCell::new(generator)) + }; + let gas_counter = Gas::default(); // maybe const? + + // Snapshot of effects before execution, so in case of error only nonce update + // can be returned. + let effect_snapshot = tracking_copy.borrow().effect(); + + let (instance, mut runtime) = self + .create_runtime( + module, + EntryPointType::Contract, + runtime_args.clone(), + named_keys, + extra_keys, + base_key, + account, + authorization_keys, + blocktime, + deploy_hash, + gas_limit, + hash_address_generator, + uref_address_generator, + protocol_version, + correlation_id, + tracking_copy, + phase, + protocol_data, + system_contract_cache, + ) + .map_err(|e| { + ExecutionResult::Failure { + effect: effect_snapshot.clone(), + cost: gas_counter, + error: e.into(), + } + .take_without_ret::(); + }) + .unwrap(); + + if !self.config.use_system_contracts() { + let mut inner_named_keys = runtime.context().named_keys().clone(); + let ret = direct_system_contract_call.host_exec( + runtime, + protocol_version, + &mut inner_named_keys, + &runtime_args, + extra_keys, + effect_snapshot, + ); + *named_keys = inner_named_keys; + return ret; + } + + let (maybe_ret, maybe_error, revert_effect): (Option, Option, bool) = { + match instance.invoke_export( + direct_system_contract_call.entry_point_name(), + &[], + &mut runtime, + ) { + Err(error) => match error.as_host_error() { + Some(host_error) => match host_error.downcast_ref::().unwrap() { + Error::Ret(ref ret_urefs) => match runtime.take_host_buffer() { + Some(result) => match result.into_t() { + Ok(ret) => { + let ret_urefs_map: HashMap> = + extract_access_rights_from_urefs(ret_urefs.clone()); + runtime.access_rights_extend(ret_urefs_map); + + (Some(ret), None, false) + } + Err(error) => (None, Some(Error::CLValue(error)), false), + }, + None => (None, Some(Error::ExpectedReturnValue), false), + }, + Error::Revert(api_error) => (None, Some(Error::Revert(*api_error)), true), + error => (None, Some(error.clone()), true), + }, + None => (None, Some(Error::Interpreter(error.into())), false), + }, + Ok(_) => { + match runtime.take_host_buffer() { + None => (None, None, false), // success, no ret + Some(result) => match result.into_t() { + Ok(ret) => (Some(ret), None, false), + Err(error) => (None, Some(Error::CLValue(error)), false), + }, + } + } + } + }; + + let runtime_context = runtime.context(); + + let cost = runtime_context.gas_counter(); + + let effect = if revert_effect { + effect_snapshot + } else { + runtime_context.effect() + }; + + let execution_result = match maybe_error { + Some(error) => ExecutionResult::Failure { + error: error.into(), + effect, + cost, + }, + None => ExecutionResult::Success { effect, cost }, + }; + + match maybe_ret { + Some(ret) => execution_result.take_with_ret(ret), + None => execution_result.take_without_ret(), + } + } + + /// Used to execute arbitrary wasm; necessary for running system contract installers / upgraders + /// This is not meant to be used for executing system contracts. + pub fn exec_wasm_direct( + &self, + module: Module, + entry_point_name: &str, + args: RuntimeArgs, + account: &mut Account, + authorization_keys: BTreeSet, + blocktime: BlockTime, + deploy_hash: [u8; 32], + gas_limit: Gas, + hash_address_generator: Rc>, + uref_address_generator: Rc>, + protocol_version: ProtocolVersion, + correlation_id: CorrelationId, + tracking_copy: Rc>>, + phase: Phase, + protocol_data: ProtocolData, + system_contract_cache: SystemContractCache, + ) -> Result + where + R: StateReader, + R::Error: Into, + T: FromBytes + CLTyped, + { + let mut named_keys: NamedKeys = account.named_keys().clone(); + let base_key = account.account_hash().into(); + + let (instance, mut runtime) = self.create_runtime( + module, + EntryPointType::Session, + args, + &mut named_keys, + Default::default(), + base_key, + account, + authorization_keys, + blocktime, + deploy_hash, + gas_limit, + hash_address_generator, + uref_address_generator, + protocol_version, + correlation_id, + tracking_copy, + phase, + protocol_data, + system_contract_cache, + )?; + + let error: wasmi::Error = match instance.invoke_export(entry_point_name, &[], &mut runtime) + { + Err(error) => error, + Ok(_) => { + // This duplicates the behavior of runtime sub_call. + // If `instance.invoke_export` returns `Ok` and the `host_buffer` is `None`, the + // contract's execution succeeded but did not explicitly call `runtime::ret()`. + // Treat as though the execution returned the unit type `()` as per Rust + // functions which don't specify a return value. + let result = runtime.take_host_buffer().unwrap_or(CLValue::from_t(())?); + let ret = result.into_t()?; + *account.named_keys_mut() = named_keys; + return Ok(ret); + } + }; + + let return_value: CLValue = match error + .as_host_error() + .and_then(|host_error| host_error.downcast_ref::()) + { + Some(Error::Ret(_)) => runtime + .take_host_buffer() + .ok_or(Error::ExpectedReturnValue)?, + Some(Error::Revert(code)) => return Err(Error::Revert(*code)), + Some(error) => return Err(error.clone()), + _ => return Err(Error::Interpreter(error.into())), + }; + + let ret = return_value.into_t()?; + *account.named_keys_mut() = named_keys; + Ok(ret) + } + + pub fn create_runtime<'a, R>( + &self, + module: Module, + entry_point_type: EntryPointType, + runtime_args: RuntimeArgs, + named_keys: &'a mut NamedKeys, + extra_keys: &[Key], + base_key: Key, + account: &'a Account, + authorization_keys: BTreeSet, + blocktime: BlockTime, + deploy_hash: [u8; 32], + gas_limit: Gas, + hash_address_generator: Rc>, + uref_address_generator: Rc>, + protocol_version: ProtocolVersion, + correlation_id: CorrelationId, + tracking_copy: Rc>>, + phase: Phase, + protocol_data: ProtocolData, + system_contract_cache: SystemContractCache, + ) -> Result<(ModuleRef, Runtime<'a, R>), Error> + where + R: StateReader, + R::Error: Into, + { + let access_rights = { + let mut keys: Vec = named_keys.values().cloned().collect(); + keys.extend(extra_keys); + extract_access_rights_from_keys(keys) + }; + + let gas_counter = Gas::default(); + + let runtime_context = RuntimeContext::new( + tracking_copy, + entry_point_type, + named_keys, + access_rights, + runtime_args, + authorization_keys, + account, + base_key, + blocktime, + deploy_hash, + gas_limit, + gas_counter, + hash_address_generator, + uref_address_generator, + protocol_version, + correlation_id, + phase, + protocol_data, + ); + + let (instance, memory) = instance_and_memory(module.clone(), protocol_version)?; + + let runtime = Runtime::new( + self.config, + system_contract_cache, + memory, + module, + runtime_context, + ); + + Ok((instance, runtime)) + } +} + +pub enum DirectSystemContractCall { + FinalizePayment, + CreatePurse, + Transfer, +} + +impl DirectSystemContractCall { + fn entry_point_name(&self) -> &str { + match self { + DirectSystemContractCall::FinalizePayment => "finalize_payment", + DirectSystemContractCall::CreatePurse => "create", + DirectSystemContractCall::Transfer => "transfer", + } + } + + fn host_exec( + &self, + mut runtime: Runtime, + protocol_version: ProtocolVersion, + named_keys: &mut NamedKeys, + runtime_args: &RuntimeArgs, + extra_keys: &[Key], + execution_effect: ExecutionEffect, + ) -> (Option, ExecutionResult) + where + R: StateReader, + R::Error: Into, + T: FromBytes + CLTyped, + { + let entry_point_name = self.entry_point_name(); + let result = match self { + DirectSystemContractCall::FinalizePayment => runtime.call_host_proof_of_stake( + protocol_version, + entry_point_name, + named_keys, + runtime_args, + extra_keys, + ), + DirectSystemContractCall::CreatePurse | DirectSystemContractCall::Transfer => runtime + .call_host_mint( + protocol_version, + entry_point_name, + named_keys, + runtime_args, + extra_keys, + ), + }; + + match result { + Ok(value) => match value.into_t() { + Ok(ret) => ExecutionResult::Success { + effect: runtime.context().effect(), + cost: runtime.context().gas_counter(), + } + .take_with_ret(ret), + Err(error) => ExecutionResult::Failure { + error: Error::CLValue(error).into(), + effect: execution_effect, + cost: runtime.context().gas_counter(), + } + .take_without_ret(), + }, + Err(error) => ExecutionResult::Failure { + error: error.into(), + effect: execution_effect, + cost: runtime.context().gas_counter(), + } + .take_without_ret(), + } + } +} diff --git a/node/src/contract_core/execution/mod.rs b/node/src/contract_core/execution/mod.rs new file mode 100644 index 0000000000..b6103640aa --- /dev/null +++ b/node/src/contract_core/execution/mod.rs @@ -0,0 +1,12 @@ +mod address_generator; +mod error; +#[macro_use] +mod executor; +#[cfg(test)] +mod tests; + +pub use self::{ + address_generator::{AddressGenerator, AddressGeneratorBuilder}, + error::Error, + executor::{DirectSystemContractCall, Executor}, +}; diff --git a/node/src/contract_core/execution/tests.rs b/node/src/contract_core/execution/tests.rs new file mode 100644 index 0000000000..d875fc31e0 --- /dev/null +++ b/node/src/contract_core/execution/tests.rs @@ -0,0 +1,69 @@ +use crate::contract_shared::{gas::Gas, transform::Transform}; +use tracing::warn; +use types::{Key, U512}; + +use super::Error; +use crate::contract_core::engine_state::{ + execution_effect::ExecutionEffect, execution_result::ExecutionResult, op::Op, +}; + +fn on_fail_charge_test_helper( + f: impl Fn() -> Result, + success_cost: Gas, + error_cost: Gas, +) -> ExecutionResult { + let _result = on_fail_charge!(f(), error_cost); + ExecutionResult::Success { + effect: Default::default(), + cost: success_cost, + } +} + +#[test] +fn on_fail_charge_ok_test() { + let val = Gas::new(U512::from(123)); + match on_fail_charge_test_helper(|| Ok(()), val, Gas::new(U512::from(456))) { + ExecutionResult::Success { cost, .. } => assert_eq!(cost, val), + ExecutionResult::Failure { .. } => panic!("Should be success"), + } +} + +#[test] +fn on_fail_charge_err_laziness_test() { + let input: Result<(), Error> = Err(Error::GasLimit); + let error_cost = Gas::new(U512::from(456)); + match on_fail_charge_test_helper(|| input.clone(), Gas::new(U512::from(123)), error_cost) { + ExecutionResult::Success { .. } => panic!("Should fail"), + ExecutionResult::Failure { cost, .. } => assert_eq!(cost, error_cost), + } +} + +#[test] +fn on_fail_charge_with_action() { + let f = || { + let input: Result<(), Error> = Err(Error::GasLimit); + on_fail_charge!(input, Gas::new(U512::from(456)), { + let mut effect = ExecutionEffect::default(); + + effect.ops.insert(Key::Hash([42u8; 32]), Op::Read); + effect + .transforms + .insert(Key::Hash([42u8; 32]), Transform::Identity); + + effect + }); + ExecutionResult::Success { + effect: Default::default(), + cost: Gas::default(), + } + }; + match f() { + ExecutionResult::Success { .. } => panic!("Should fail"), + ExecutionResult::Failure { cost, effect, .. } => { + assert_eq!(cost, Gas::new(U512::from(456))); + // Check if the containers are non-empty + assert_eq!(effect.ops.len(), 1); + assert_eq!(effect.transforms.len(), 1); + } + } +} diff --git a/node/src/contract_core/resolvers/error.rs b/node/src/contract_core/resolvers/error.rs new file mode 100644 index 0000000000..7bf287ff5f --- /dev/null +++ b/node/src/contract_core/resolvers/error.rs @@ -0,0 +1,11 @@ +use failure::Fail; + +use types::ProtocolVersion; + +#[derive(Fail, Debug, Copy, Clone)] +pub enum ResolverError { + #[fail(display = "Unknown protocol version: {}", _0)] + UnknownProtocolVersion(ProtocolVersion), + #[fail(display = "No imported memory")] + NoImportedMemory, +} diff --git a/node/src/contract_core/resolvers/memory_resolver.rs b/node/src/contract_core/resolvers/memory_resolver.rs new file mode 100644 index 0000000000..5caf1d871b --- /dev/null +++ b/node/src/contract_core/resolvers/memory_resolver.rs @@ -0,0 +1,11 @@ +use wasmi::MemoryRef; + +use super::error::ResolverError; + +/// This trait takes care of returning an instance of allocated memory. +/// +/// This happens once the WASM program tries to resolve "memory". Whenever +/// contract didn't request a memory this method should return an Error. +pub trait MemoryResolver { + fn memory_ref(&self) -> Result; +} diff --git a/node/src/contract_core/resolvers/mod.rs b/node/src/contract_core/resolvers/mod.rs new file mode 100644 index 0000000000..a75ece1e1d --- /dev/null +++ b/node/src/contract_core/resolvers/mod.rs @@ -0,0 +1,34 @@ +pub mod error; +pub mod memory_resolver; +pub mod v1_function_index; +mod v1_resolver; + +use wasmi::ModuleImportResolver; + +use types::ProtocolVersion; + +use self::error::ResolverError; +use crate::contract_core::resolvers::memory_resolver::MemoryResolver; + +/// Creates a module resolver for given protocol version. +/// +/// * `protocol_version` Version of the protocol. Can't be lower than 1. +pub fn create_module_resolver( + protocol_version: ProtocolVersion, +) -> Result { + // TODO: revisit how protocol_version check here is meant to combine with upgrade + if protocol_version >= ProtocolVersion::V1_0_0 { + return Ok(v1_resolver::RuntimeModuleImportResolver::default()); + } + Err(ResolverError::UnknownProtocolVersion(protocol_version)) +} + +#[test] +fn resolve_invalid_module() { + assert!(create_module_resolver(ProtocolVersion::default()).is_err()); +} + +#[test] +fn protocol_version_1_always_resolves() { + assert!(create_module_resolver(ProtocolVersion::V1_0_0).is_ok()); +} diff --git a/node/src/contract_core/resolvers/v1_function_index.rs b/node/src/contract_core/resolvers/v1_function_index.rs new file mode 100644 index 0000000000..14f8040a1f --- /dev/null +++ b/node/src/contract_core/resolvers/v1_function_index.rs @@ -0,0 +1,89 @@ +use std::convert::TryFrom; + +use num_derive::{FromPrimitive, ToPrimitive}; +use num_traits::{FromPrimitive, ToPrimitive}; + +#[derive(Debug, PartialEq, FromPrimitive, ToPrimitive, Clone, Copy)] +#[repr(usize)] +pub enum FunctionIndex { + WriteFuncIndex, + WriteLocalFuncIndex, + ReadFuncIndex, + ReadLocalFuncIndex, + AddFuncIndex, + NewFuncIndex, + RetFuncIndex, + CallContractFuncIndex, + GetKeyFuncIndex, + GasFuncIndex, + HasKeyFuncIndex, + PutKeyFuncIndex, + IsValidURefFnIndex, + RevertFuncIndex, + AddAssociatedKeyFuncIndex, + RemoveAssociatedKeyFuncIndex, + UpdateAssociatedKeyFuncIndex, + SetActionThresholdFuncIndex, + LoadNamedKeysFuncIndex, + RemoveKeyFuncIndex, + GetCallerIndex, + GetBlocktimeIndex, + CreatePurseIndex, + TransferToAccountIndex, + TransferFromPurseToAccountIndex, + TransferFromPurseToPurseIndex, + GetBalanceIndex, + GetPhaseIndex, + GetSystemContractIndex, + GetMainPurseIndex, + ReadHostBufferIndex, + CreateContractPackageAtHash, + AddContractVersion, + DisableContractVersion, + CallVersionedContract, + CreateContractUserGroup, + #[cfg(feature = "test-support")] + PrintIndex, + GetRuntimeArgsizeIndex, + GetRuntimeArgIndex, + RemoveContractUserGroupIndex, + ExtendContractUserGroupURefsIndex, + RemoveContractUserGroupURefsIndex, +} + +impl Into for FunctionIndex { + fn into(self) -> usize { + // NOTE: This can't fail as `FunctionIndex` is represented by usize, + // so this serves mostly as a syntax sugar. + self.to_usize().unwrap() + } +} + +impl TryFrom for FunctionIndex { + type Error = &'static str; + fn try_from(value: usize) -> Result { + FromPrimitive::from_usize(value).ok_or("Invalid function index") + } +} + +#[cfg(test)] +mod tests { + use super::FunctionIndex; + use std::convert::TryFrom; + + #[test] + fn primitive_to_enum() { + FunctionIndex::try_from(19).expect("Unable to create enum from number"); + } + + #[test] + fn enum_to_primitive() { + let element = FunctionIndex::UpdateAssociatedKeyFuncIndex; + let _primitive: usize = element.into(); + } + + #[test] + fn invalid_index() { + assert!(FunctionIndex::try_from(123_456_789usize).is_err()); + } +} diff --git a/node/src/contract_core/resolvers/v1_resolver.rs b/node/src/contract_core/resolvers/v1_resolver.rs new file mode 100644 index 0000000000..496b37480c --- /dev/null +++ b/node/src/contract_core/resolvers/v1_resolver.rs @@ -0,0 +1,248 @@ +use std::cell::RefCell; + +use wasmi::{ + memory_units::Pages, Error as InterpreterError, FuncInstance, FuncRef, MemoryDescriptor, + MemoryInstance, MemoryRef, ModuleImportResolver, Signature, ValueType, +}; + +use super::{ + error::ResolverError, memory_resolver::MemoryResolver, v1_function_index::FunctionIndex, +}; + +pub(crate) struct RuntimeModuleImportResolver { + memory: RefCell>, + max_memory: u32, +} + +impl Default for RuntimeModuleImportResolver { + fn default() -> Self { + RuntimeModuleImportResolver { + memory: RefCell::new(None), + max_memory: 64, + } + } +} + +impl MemoryResolver for RuntimeModuleImportResolver { + fn memory_ref(&self) -> Result { + self.memory + .borrow() + .as_ref() + .map(Clone::clone) + .ok_or(ResolverError::NoImportedMemory) + } +} + +impl ModuleImportResolver for RuntimeModuleImportResolver { + fn resolve_func( + &self, + field_name: &str, + _signature: &Signature, + ) -> Result { + let func_ref = match field_name { + "read_value" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 3][..], Some(ValueType::I32)), + FunctionIndex::ReadFuncIndex.into(), + ), + "read_value_local" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 3][..], Some(ValueType::I32)), + FunctionIndex::ReadLocalFuncIndex.into(), + ), + "load_named_keys" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], Some(ValueType::I32)), + FunctionIndex::LoadNamedKeysFuncIndex.into(), + ), + "write" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 4][..], None), + FunctionIndex::WriteFuncIndex.into(), + ), + "write_local" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 4][..], None), + FunctionIndex::WriteLocalFuncIndex.into(), + ), + "add" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 4][..], None), + FunctionIndex::AddFuncIndex.into(), + ), + "new_uref" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 3][..], None), + FunctionIndex::NewFuncIndex.into(), + ), + "ret" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], None), + FunctionIndex::RetFuncIndex.into(), + ), + "get_key" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 5][..], Some(ValueType::I32)), + FunctionIndex::GetKeyFuncIndex.into(), + ), + "has_key" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], Some(ValueType::I32)), + FunctionIndex::HasKeyFuncIndex.into(), + ), + "put_key" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 4][..], None), + FunctionIndex::PutKeyFuncIndex.into(), + ), + "gas" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 1][..], None), + FunctionIndex::GasFuncIndex.into(), + ), + "is_valid_uref" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], Some(ValueType::I32)), + FunctionIndex::IsValidURefFnIndex.into(), + ), + "revert" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 1][..], None), + FunctionIndex::RevertFuncIndex.into(), + ), + "add_associated_key" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 3][..], Some(ValueType::I32)), + FunctionIndex::AddAssociatedKeyFuncIndex.into(), + ), + "remove_associated_key" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], Some(ValueType::I32)), + FunctionIndex::RemoveAssociatedKeyFuncIndex.into(), + ), + "update_associated_key" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 3][..], Some(ValueType::I32)), + FunctionIndex::UpdateAssociatedKeyFuncIndex.into(), + ), + "set_action_threshold" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], Some(ValueType::I32)), + FunctionIndex::SetActionThresholdFuncIndex.into(), + ), + "remove_key" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], None), + FunctionIndex::RemoveKeyFuncIndex.into(), + ), + "get_caller" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 1][..], Some(ValueType::I32)), + FunctionIndex::GetCallerIndex.into(), + ), + "get_blocktime" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 1][..], None), + FunctionIndex::GetBlocktimeIndex.into(), + ), + "create_purse" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], Some(ValueType::I32)), + FunctionIndex::CreatePurseIndex.into(), + ), + "transfer_to_account" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 4][..], Some(ValueType::I32)), + FunctionIndex::TransferToAccountIndex.into(), + ), + "transfer_from_purse_to_account" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 6][..], Some(ValueType::I32)), + FunctionIndex::TransferFromPurseToAccountIndex.into(), + ), + "transfer_from_purse_to_purse" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 6][..], Some(ValueType::I32)), + FunctionIndex::TransferFromPurseToPurseIndex.into(), + ), + "get_balance" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 3][..], Some(ValueType::I32)), + FunctionIndex::GetBalanceIndex.into(), + ), + "get_phase" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 1][..], None), + FunctionIndex::GetPhaseIndex.into(), + ), + "get_system_contract" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 3][..], Some(ValueType::I32)), + FunctionIndex::GetSystemContractIndex.into(), + ), + "get_main_purse" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 1][..], None), + FunctionIndex::GetMainPurseIndex.into(), + ), + "read_host_buffer" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 3][..], Some(ValueType::I32)), + FunctionIndex::ReadHostBufferIndex.into(), + ), + "create_contract_package_at_hash" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], None), + FunctionIndex::CreateContractPackageAtHash.into(), + ), + "create_contract_user_group" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 8][..], Some(ValueType::I32)), + FunctionIndex::CreateContractUserGroup.into(), + ), + "add_contract_version" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 10][..], Some(ValueType::I32)), + FunctionIndex::AddContractVersion.into(), + ), + "disable_contract_version" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 4][..], Some(ValueType::I32)), + FunctionIndex::DisableContractVersion.into(), + ), + "call_contract" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 7][..], Some(ValueType::I32)), + FunctionIndex::CallContractFuncIndex.into(), + ), + "call_versioned_contract" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 9][..], Some(ValueType::I32)), + FunctionIndex::CallVersionedContract.into(), + ), + "get_named_arg_size" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 3][..], Some(ValueType::I32)), + FunctionIndex::GetRuntimeArgsizeIndex.into(), + ), + "get_named_arg" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 4][..], Some(ValueType::I32)), + FunctionIndex::GetRuntimeArgIndex.into(), + ), + "remove_contract_user_group" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 4][..], Some(ValueType::I32)), + FunctionIndex::RemoveContractUserGroupIndex.into(), + ), + "provision_contract_user_group_uref" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 5][..], Some(ValueType::I32)), + FunctionIndex::ExtendContractUserGroupURefsIndex.into(), + ), + "remove_contract_user_group_urefs" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 6][..], Some(ValueType::I32)), + FunctionIndex::RemoveContractUserGroupURefsIndex.into(), + ), + #[cfg(feature = "test-support")] + "print" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 2][..], None), + FunctionIndex::PrintIndex.into(), + ), + _ => { + return Err(InterpreterError::Function(format!( + "host module doesn't export function with name {}", + field_name + ))); + } + }; + Ok(func_ref) + } + + fn resolve_memory( + &self, + field_name: &str, + descriptor: &MemoryDescriptor, + ) -> Result { + if field_name == "memory" { + let effective_max = descriptor.maximum().unwrap_or(self.max_memory + 1); + if descriptor.initial() > self.max_memory || effective_max > self.max_memory { + Err(InterpreterError::Instantiation( + "Module requested too much memory".to_owned(), + )) + } else { + // Note: each "page" is 64 KiB + let mem = MemoryInstance::alloc( + Pages(descriptor.initial() as usize), + descriptor.maximum().map(|x| Pages(x as usize)), + )?; + *self.memory.borrow_mut() = Some(mem.clone()); + Ok(mem) + } + } else { + Err(InterpreterError::Instantiation( + "Memory imported under unknown name".to_owned(), + )) + } + } +} diff --git a/node/src/contract_core/runtime/args.rs b/node/src/contract_core/runtime/args.rs new file mode 100644 index 0000000000..330c92a968 --- /dev/null +++ b/node/src/contract_core/runtime/args.rs @@ -0,0 +1,232 @@ +use wasmi::{FromRuntimeValue, RuntimeArgs, Trap}; + +pub(crate) trait Args +where + Self: Sized, +{ + fn parse(args: RuntimeArgs) -> Result; +} + +impl Args for u32 { + fn parse(args: RuntimeArgs) -> Result { + args.nth_checked(0) + } +} + +impl Args for usize { + fn parse(args: RuntimeArgs) -> Result { + let a0: u32 = args.nth_checked(0)?; + Ok(a0 as usize) + } +} + +impl Args for (T1, T2) +where + T1: FromRuntimeValue + Sized, + T2: FromRuntimeValue + Sized, +{ + fn parse(args: RuntimeArgs) -> Result { + let a0: T1 = args.nth_checked(0)?; + let a1: T2 = args.nth_checked(1)?; + Ok((a0, a1)) + } +} + +impl Args for (T1, T2, T3) +where + T1: FromRuntimeValue + Sized, + T2: FromRuntimeValue + Sized, + T3: FromRuntimeValue + Sized, +{ + fn parse(args: RuntimeArgs) -> Result { + let a0: T1 = args.nth_checked(0)?; + let a1: T2 = args.nth_checked(1)?; + let a2: T3 = args.nth_checked(2)?; + Ok((a0, a1, a2)) + } +} + +impl Args for (T1, T2, T3, T4) +where + T1: FromRuntimeValue + Sized, + T2: FromRuntimeValue + Sized, + T3: FromRuntimeValue + Sized, + T4: FromRuntimeValue + Sized, +{ + fn parse(args: RuntimeArgs) -> Result { + let a0: T1 = args.nth_checked(0)?; + let a1: T2 = args.nth_checked(1)?; + let a2: T3 = args.nth_checked(2)?; + let a3: T4 = args.nth_checked(3)?; + Ok((a0, a1, a2, a3)) + } +} + +impl Args for (T1, T2, T3, T4, T5) +where + T1: FromRuntimeValue + Sized, + T2: FromRuntimeValue + Sized, + T3: FromRuntimeValue + Sized, + T4: FromRuntimeValue + Sized, + T5: FromRuntimeValue + Sized, +{ + fn parse(args: RuntimeArgs) -> Result { + let a0: T1 = args.nth_checked(0)?; + let a1: T2 = args.nth_checked(1)?; + let a2: T3 = args.nth_checked(2)?; + let a3: T4 = args.nth_checked(3)?; + let a4: T5 = args.nth_checked(4)?; + Ok((a0, a1, a2, a3, a4)) + } +} + +impl Args for (T1, T2, T3, T4, T5, T6) +where + T1: FromRuntimeValue + Sized, + T2: FromRuntimeValue + Sized, + T3: FromRuntimeValue + Sized, + T4: FromRuntimeValue + Sized, + T5: FromRuntimeValue + Sized, + T6: FromRuntimeValue + Sized, +{ + fn parse(args: RuntimeArgs) -> Result { + let a0: T1 = args.nth_checked(0)?; + let a1: T2 = args.nth_checked(1)?; + let a2: T3 = args.nth_checked(2)?; + let a3: T4 = args.nth_checked(3)?; + let a4: T5 = args.nth_checked(4)?; + let a5: T6 = args.nth_checked(5)?; + Ok((a0, a1, a2, a3, a4, a5)) + } +} + +impl Args for (T1, T2, T3, T4, T5, T6, T7) +where + T1: FromRuntimeValue + Sized, + T2: FromRuntimeValue + Sized, + T3: FromRuntimeValue + Sized, + T4: FromRuntimeValue + Sized, + T5: FromRuntimeValue + Sized, + T6: FromRuntimeValue + Sized, + T7: FromRuntimeValue + Sized, +{ + fn parse(args: RuntimeArgs) -> Result { + let a0: T1 = args.nth_checked(0)?; + let a1: T2 = args.nth_checked(1)?; + let a2: T3 = args.nth_checked(2)?; + let a3: T4 = args.nth_checked(3)?; + let a4: T5 = args.nth_checked(4)?; + let a5: T6 = args.nth_checked(5)?; + let a6: T7 = args.nth_checked(6)?; + Ok((a0, a1, a2, a3, a4, a5, a6)) + } +} + +impl Args for (T1, T2, T3, T4, T5, T6, T7, T8) +where + T1: FromRuntimeValue + Sized, + T2: FromRuntimeValue + Sized, + T3: FromRuntimeValue + Sized, + T4: FromRuntimeValue + Sized, + T5: FromRuntimeValue + Sized, + T6: FromRuntimeValue + Sized, + T7: FromRuntimeValue + Sized, + T8: FromRuntimeValue + Sized, +{ + fn parse(args: RuntimeArgs) -> Result { + let a0: T1 = args.nth_checked(0)?; + let a1: T2 = args.nth_checked(1)?; + let a2: T3 = args.nth_checked(2)?; + let a3: T4 = args.nth_checked(3)?; + let a4: T5 = args.nth_checked(4)?; + let a5: T6 = args.nth_checked(5)?; + let a6: T7 = args.nth_checked(6)?; + let a7: T8 = args.nth_checked(7)?; + Ok((a0, a1, a2, a3, a4, a5, a6, a7)) + } +} + +impl Args for (T1, T2, T3, T4, T5, T6, T7, T8, T9) +where + T1: FromRuntimeValue + Sized, + T2: FromRuntimeValue + Sized, + T3: FromRuntimeValue + Sized, + T4: FromRuntimeValue + Sized, + T5: FromRuntimeValue + Sized, + T6: FromRuntimeValue + Sized, + T7: FromRuntimeValue + Sized, + T8: FromRuntimeValue + Sized, + T9: FromRuntimeValue + Sized, +{ + fn parse(args: RuntimeArgs) -> Result { + let a0: T1 = args.nth_checked(0)?; + let a1: T2 = args.nth_checked(1)?; + let a2: T3 = args.nth_checked(2)?; + let a3: T4 = args.nth_checked(3)?; + let a4: T5 = args.nth_checked(4)?; + let a5: T6 = args.nth_checked(5)?; + let a6: T7 = args.nth_checked(6)?; + let a7: T8 = args.nth_checked(7)?; + let a8: T9 = args.nth_checked(8)?; + Ok((a0, a1, a2, a3, a4, a5, a6, a7, a8)) + } +} + +impl Args for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) +where + T1: FromRuntimeValue + Sized, + T2: FromRuntimeValue + Sized, + T3: FromRuntimeValue + Sized, + T4: FromRuntimeValue + Sized, + T5: FromRuntimeValue + Sized, + T6: FromRuntimeValue + Sized, + T7: FromRuntimeValue + Sized, + T8: FromRuntimeValue + Sized, + T9: FromRuntimeValue + Sized, + T10: FromRuntimeValue + Sized, +{ + fn parse(args: RuntimeArgs) -> Result { + let a0: T1 = args.nth_checked(0)?; + let a1: T2 = args.nth_checked(1)?; + let a2: T3 = args.nth_checked(2)?; + let a3: T4 = args.nth_checked(3)?; + let a4: T5 = args.nth_checked(4)?; + let a5: T6 = args.nth_checked(5)?; + let a6: T7 = args.nth_checked(6)?; + let a7: T8 = args.nth_checked(7)?; + let a8: T9 = args.nth_checked(8)?; + let a9: T10 = args.nth_checked(9)?; + Ok((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9)) + } +} + +impl Args + for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) +where + T1: FromRuntimeValue + Sized, + T2: FromRuntimeValue + Sized, + T3: FromRuntimeValue + Sized, + T4: FromRuntimeValue + Sized, + T5: FromRuntimeValue + Sized, + T6: FromRuntimeValue + Sized, + T7: FromRuntimeValue + Sized, + T8: FromRuntimeValue + Sized, + T9: FromRuntimeValue + Sized, + T10: FromRuntimeValue + Sized, + T11: FromRuntimeValue + Sized, +{ + fn parse(args: RuntimeArgs) -> Result { + let a0: T1 = args.nth_checked(0)?; + let a1: T2 = args.nth_checked(1)?; + let a2: T3 = args.nth_checked(2)?; + let a3: T4 = args.nth_checked(3)?; + let a4: T5 = args.nth_checked(4)?; + let a5: T6 = args.nth_checked(5)?; + let a6: T7 = args.nth_checked(6)?; + let a7: T8 = args.nth_checked(7)?; + let a8: T9 = args.nth_checked(8)?; + let a9: T10 = args.nth_checked(9)?; + let a10: T11 = args.nth_checked(10)?; + Ok((a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)) + } +} diff --git a/node/src/contract_core/runtime/externals.rs b/node/src/contract_core/runtime/externals.rs new file mode 100644 index 0000000000..0fce2985f8 --- /dev/null +++ b/node/src/contract_core/runtime/externals.rs @@ -0,0 +1,672 @@ +use std::{collections::BTreeSet, convert::TryFrom}; + +use wasmi::{Externals, RuntimeArgs, RuntimeValue, Trap}; + +use types::{ + account::AccountHash, + api_error, + bytesrepr::{self, ToBytes}, + contracts::{EntryPoints, NamedKeys}, + ContractHash, ContractPackageHash, ContractVersion, Group, Key, TransferredTo, URef, U512, +}; + +use crate::contract_shared::{gas::Gas, stored_value::StoredValue}; +use crate::contract_storage::global_state::StateReader; + +use super::{args::Args, scoped_instrumenter::ScopedInstrumenter, Error, Runtime}; +use crate::contract_core::resolvers::v1_function_index::FunctionIndex; + +impl<'a, R> Externals for Runtime<'a, R> +where + R: StateReader, + R::Error: Into, +{ + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> Result, Trap> { + let func = FunctionIndex::try_from(index).expect("unknown function index"); + let mut scoped_instrumenter = ScopedInstrumenter::new(func); + match func { + FunctionIndex::ReadFuncIndex => { + // args(0) = pointer to key in Wasm memory + // args(1) = size of key in Wasm memory + // args(2) = pointer to output size (output param) + let (key_ptr, key_size, output_size_ptr) = Args::parse(args)?; + let ret = self.read(key_ptr, key_size, output_size_ptr)?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::ReadLocalFuncIndex => { + // args(0) = pointer to key in Wasm memory + // args(1) = size of key in Wasm memory + // args(2) = pointer to output size (output param) + let (key_ptr, key_size, output_size_ptr): (_, u32, _) = Args::parse(args)?; + scoped_instrumenter.add_property("key_size", key_size); + let ret = self.read_local(key_ptr, key_size, output_size_ptr)?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::LoadNamedKeysFuncIndex => { + // args(0) = pointer to amount of keys (output) + // args(1) = pointer to amount of serialized bytes (output) + let (total_keys_ptr, result_size_ptr) = Args::parse(args)?; + let ret = self.load_named_keys( + total_keys_ptr, + result_size_ptr, + &mut scoped_instrumenter, + )?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::WriteFuncIndex => { + // args(0) = pointer to key in Wasm memory + // args(1) = size of key + // args(2) = pointer to value + // args(3) = size of value + let (key_ptr, key_size, value_ptr, value_size): (_, _, _, u32) = Args::parse(args)?; + scoped_instrumenter.add_property("value_size", value_size); + self.write(key_ptr, key_size, value_ptr, value_size)?; + Ok(None) + } + + FunctionIndex::WriteLocalFuncIndex => { + // args(0) = pointer to key in Wasm memory + // args(1) = size of key + // args(2) = pointer to value + // args(3) = size of value + let (key_bytes_ptr, key_bytes_size, value_ptr, value_size): (_, u32, _, u32) = + Args::parse(args)?; + scoped_instrumenter.add_property("key_bytes_size", key_bytes_size); + scoped_instrumenter.add_property("value_size", value_size); + self.write_local(key_bytes_ptr, key_bytes_size, value_ptr, value_size)?; + Ok(None) + } + + FunctionIndex::AddFuncIndex => { + // args(0) = pointer to key in Wasm memory + // args(1) = size of key + // args(2) = pointer to value + // args(3) = size of value + let (key_ptr, key_size, value_ptr, value_size) = Args::parse(args)?; + self.add(key_ptr, key_size, value_ptr, value_size)?; + Ok(None) + } + + FunctionIndex::NewFuncIndex => { + // args(0) = pointer to uref destination in Wasm memory + // args(1) = pointer to initial value + // args(2) = size of initial value + let (uref_ptr, value_ptr, value_size): (_, _, u32) = Args::parse(args)?; + scoped_instrumenter.add_property("value_size", value_size); + self.new_uref(uref_ptr, value_ptr, value_size)?; + Ok(None) + } + + FunctionIndex::RetFuncIndex => { + // args(0) = pointer to value + // args(1) = size of value + let (value_ptr, value_size): (_, u32) = Args::parse(args)?; + scoped_instrumenter.add_property("value_size", value_size); + Err(self.ret(value_ptr, value_size as usize, &mut scoped_instrumenter)) + } + + FunctionIndex::GetKeyFuncIndex => { + // args(0) = pointer to key name in Wasm memory + // args(1) = size of key name + // args(2) = pointer to output buffer for serialized key + // args(3) = size of output buffer + // args(4) = pointer to bytes written + let (name_ptr, name_size, output_ptr, output_size, bytes_written): ( + u32, + u32, + u32, + u32, + u32, + ) = Args::parse(args)?; + scoped_instrumenter.add_property("name_size", name_size); + let ret = self.load_key( + name_ptr, + name_size, + output_ptr, + output_size as usize, + bytes_written, + )?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::HasKeyFuncIndex => { + // args(0) = pointer to key name in Wasm memory + // args(1) = size of key name + let (name_ptr, name_size): (_, u32) = Args::parse(args)?; + scoped_instrumenter.add_property("name_size", name_size); + let result = self.has_key(name_ptr, name_size)?; + Ok(Some(RuntimeValue::I32(result))) + } + + FunctionIndex::PutKeyFuncIndex => { + // args(0) = pointer to key name in Wasm memory + // args(1) = size of key name + // args(2) = pointer to key in Wasm memory + // args(3) = size of key + let (name_ptr, name_size, key_ptr, key_size): (_, u32, _, _) = Args::parse(args)?; + scoped_instrumenter.add_property("name_size", name_size); + self.put_key(name_ptr, name_size, key_ptr, key_size)?; + Ok(None) + } + + FunctionIndex::RemoveKeyFuncIndex => { + // args(0) = pointer to key name in Wasm memory + // args(1) = size of key name + let (name_ptr, name_size): (_, u32) = Args::parse(args)?; + scoped_instrumenter.add_property("name_size", name_size); + self.remove_key(name_ptr, name_size)?; + Ok(None) + } + + FunctionIndex::GetCallerIndex => { + // args(0) = pointer where a size of serialized bytes will be stored + let output_size = Args::parse(args)?; + let ret = self.get_caller(output_size)?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::GetBlocktimeIndex => { + // args(0) = pointer to Wasm memory where to write. + let dest_ptr = Args::parse(args)?; + self.get_blocktime(dest_ptr)?; + Ok(None) + } + + FunctionIndex::GasFuncIndex => { + let gas_arg: u32 = Args::parse(args)?; + self.gas(Gas::new(gas_arg.into()))?; + Ok(None) + } + + FunctionIndex::IsValidURefFnIndex => { + // args(0) = pointer to value to validate + // args(1) = size of value + let (uref_ptr, uref_size) = Args::parse(args)?; + + Ok(Some(RuntimeValue::I32(i32::from( + self.is_valid_uref(uref_ptr, uref_size)?, + )))) + } + + FunctionIndex::RevertFuncIndex => { + // args(0) = status u32 + let status = Args::parse(args)?; + + Err(self.revert(status)) + } + + FunctionIndex::AddAssociatedKeyFuncIndex => { + // args(0) = pointer to array of bytes of an account hash + // args(1) = size of an account hash + // args(2) = weight of the key + let (account_hash_ptr, account_hash_size, weight_value): (u32, u32, u8) = + Args::parse(args)?; + let value = self.add_associated_key( + account_hash_ptr, + account_hash_size as usize, + weight_value, + )?; + Ok(Some(RuntimeValue::I32(value))) + } + + FunctionIndex::RemoveAssociatedKeyFuncIndex => { + // args(0) = pointer to array of bytes of an account hash + // args(1) = size of an account hash + let (account_hash_ptr, account_hash_size): (_, u32) = Args::parse(args)?; + let value = + self.remove_associated_key(account_hash_ptr, account_hash_size as usize)?; + Ok(Some(RuntimeValue::I32(value))) + } + + FunctionIndex::UpdateAssociatedKeyFuncIndex => { + // args(0) = pointer to array of bytes of an account hash + // args(1) = size of an account hash + // args(2) = weight of the key + let (account_hash_ptr, account_hash_size, weight_value): (u32, u32, u8) = + Args::parse(args)?; + let value = self.update_associated_key( + account_hash_ptr, + account_hash_size as usize, + weight_value, + )?; + Ok(Some(RuntimeValue::I32(value))) + } + + FunctionIndex::SetActionThresholdFuncIndex => { + // args(0) = action type + // args(1) = new threshold + let (action_type_value, threshold_value): (u32, u8) = Args::parse(args)?; + let value = self.set_action_threshold(action_type_value, threshold_value)?; + Ok(Some(RuntimeValue::I32(value))) + } + + FunctionIndex::CreatePurseIndex => { + // args(0) = pointer to array for return value + // args(1) = length of array for return value + let (dest_ptr, dest_size): (u32, u32) = Args::parse(args)?; + let purse = self.create_purse()?; + let purse_bytes = purse.into_bytes().map_err(Error::BytesRepr)?; + assert_eq!(dest_size, purse_bytes.len() as u32); + self.memory + .set(dest_ptr, &purse_bytes) + .map_err(|e| Error::Interpreter(e.into()))?; + Ok(Some(RuntimeValue::I32(0))) + } + + FunctionIndex::TransferToAccountIndex => { + // args(0) = pointer to array of bytes of an account hash + // args(1) = length of array of bytes of an account hash + // args(2) = pointer to array of bytes of an amount + // args(3) = length of array of bytes of an amount + let (key_ptr, key_size, amount_ptr, amount_size): (u32, u32, u32, u32) = + Args::parse(args)?; + let account_hash: AccountHash = { + let bytes = self.bytes_from_mem(key_ptr, key_size as usize)?; + bytesrepr::deserialize(bytes).map_err(Error::BytesRepr)? + }; + let amount: U512 = { + let bytes = self.bytes_from_mem(amount_ptr, amount_size as usize)?; + bytesrepr::deserialize(bytes).map_err(Error::BytesRepr)? + }; + let ret = self.transfer_to_account(account_hash, amount)?; + Ok(Some(RuntimeValue::I32(TransferredTo::i32_from(ret)))) + } + + FunctionIndex::TransferFromPurseToAccountIndex => { + // args(0) = pointer to array of bytes in Wasm memory of a source purse + // args(1) = length of array of bytes in Wasm memory of a source purse + // args(2) = pointer to array of bytes in Wasm memory of an account hash + // args(3) = length of array of bytes in Wasm memory of an account hash + // args(4) = pointer to array of bytes in Wasm memory of an amount + // args(5) = length of array of bytes in Wasm memory of an amount + let (source_ptr, source_size, key_ptr, key_size, amount_ptr, amount_size): ( + u32, + u32, + u32, + u32, + u32, + u32, + ) = Args::parse(args)?; + + let source_purse = { + let bytes = self.bytes_from_mem(source_ptr, source_size as usize)?; + bytesrepr::deserialize(bytes).map_err(Error::BytesRepr)? + }; + let account_hash: AccountHash = { + let bytes = self.bytes_from_mem(key_ptr, key_size as usize)?; + bytesrepr::deserialize(bytes).map_err(Error::BytesRepr)? + }; + let amount: U512 = { + let bytes = self.bytes_from_mem(amount_ptr, amount_size as usize)?; + bytesrepr::deserialize(bytes).map_err(Error::BytesRepr)? + }; + let ret = + self.transfer_from_purse_to_account(source_purse, account_hash, amount)?; + Ok(Some(RuntimeValue::I32(TransferredTo::i32_from(ret)))) + } + + FunctionIndex::TransferFromPurseToPurseIndex => { + // args(0) = pointer to array of bytes in Wasm memory of a source purse + // args(1) = length of array of bytes in Wasm memory of a source purse + // args(2) = pointer to array of bytes in Wasm memory of a target purse + // args(3) = length of array of bytes in Wasm memory of a target purse + // args(4) = pointer to array of bytes in Wasm memory of an amount + // args(5) = length of array of bytes in Wasm memory of an amount + let (source_ptr, source_size, target_ptr, target_size, amount_ptr, amount_size) = + Args::parse(args)?; + let ret = self.transfer_from_purse_to_purse( + source_ptr, + source_size, + target_ptr, + target_size, + amount_ptr, + amount_size, + )?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::GetBalanceIndex => { + // args(0) = pointer to purse input + // args(1) = length of purse + // args(2) = pointer to output size (output) + let (ptr, ptr_size, output_size_ptr): (_, u32, _) = Args::parse(args)?; + let ret = self.get_balance_host_buffer(ptr, ptr_size as usize, output_size_ptr)?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::GetPhaseIndex => { + // args(0) = pointer to Wasm memory where to write. + let dest_ptr = Args::parse(args)?; + self.get_phase(dest_ptr)?; + Ok(None) + } + + FunctionIndex::GetSystemContractIndex => { + // args(0) = system contract index + // args(1) = dest pointer for storing serialized result + // args(2) = dest pointer size + let (system_contract_index, dest_ptr, dest_size) = Args::parse(args)?; + let ret = self.get_system_contract(system_contract_index, dest_ptr, dest_size)?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::GetMainPurseIndex => { + // args(0) = pointer to Wasm memory where to write. + let dest_ptr = Args::parse(args)?; + self.get_main_purse(dest_ptr)?; + Ok(None) + } + + FunctionIndex::ReadHostBufferIndex => { + // args(0) = pointer to Wasm memory where to write size. + let (dest_ptr, dest_size, bytes_written_ptr): (_, u32, _) = Args::parse(args)?; + scoped_instrumenter.add_property("dest_size", dest_size); + let ret = self.read_host_buffer(dest_ptr, dest_size as usize, bytes_written_ptr)?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::CreateContractPackageAtHash => { + // args(0) = pointer to wasm memory where to write 32-byte Hash address + // args(1) = pointer to wasm memory where to write 32-byte access key address + let (hash_dest_ptr, access_dest_ptr) = Args::parse(args)?; + let (hash_addr, access_addr) = self.create_contract_package_at_hash()?; + self.function_address(hash_addr, hash_dest_ptr)?; + self.function_address(access_addr, access_dest_ptr)?; + Ok(None) + } + + FunctionIndex::CreateContractUserGroup => { + // args(0) = pointer to package key in wasm memory + // args(1) = size of package key in wasm memory + // args(2) = pointer to group label in wasm memory + // args(3) = size of group label in wasm memory + // args(4) = number of new urefs to generate for the group + // args(5) = pointer to existing_urefs in wasm memory + // args(6) = size of existing_urefs in wasm memory + // args(7) = pointer to location to write size of output (written to host buffer) + let ( + package_key_ptr, + package_key_size, + label_ptr, + label_size, + num_new_urefs, + existing_urefs_ptr, + existing_urefs_size, + output_size_ptr, + ): (_, _, _, u32, _, _, u32, _) = Args::parse(args)?; + scoped_instrumenter + .add_property("existing_urefs_size", existing_urefs_size.to_string()); + scoped_instrumenter.add_property("label_size", label_size.to_string()); + + let contract_package_hash: ContractPackageHash = + self.t_from_mem(package_key_ptr, package_key_size)?; + let label: String = self.t_from_mem(label_ptr, label_size)?; + let existing_urefs: BTreeSet = + self.t_from_mem(existing_urefs_ptr, existing_urefs_size)?; + + let ret = self.create_contract_user_group( + contract_package_hash, + label, + num_new_urefs, + existing_urefs, + output_size_ptr, + )?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::AddContractVersion => { + // args(0) = pointer to package key in wasm memory + // args(1) = size of package key in wasm memory + // args(2) = pointer to entrypoints in wasm memory + // args(3) = size of entrypoints in wasm memory + // args(4) = pointer to named keys in wasm memory + // args(5) = size of named keys in wasm memory + // args(6) = pointer to output buffer for serialized key + // args(7) = size of output buffer + // args(8) = pointer to bytes written + let ( + contract_package_hash_ptr, + contract_package_hash_size, + version_ptr, + entry_points_ptr, + entry_points_size, + named_keys_ptr, + named_keys_size, + output_ptr, + output_size, + bytes_written_ptr, + ): (u32, u32, u32, u32, u32, u32, u32, u32, u32, u32) = Args::parse(args)?; + + scoped_instrumenter + .add_property("entry_points_size", entry_points_size.to_string()); + scoped_instrumenter.add_property("named_keys_size", named_keys_size.to_string()); + + let contract_package_hash: ContractPackageHash = + self.t_from_mem(contract_package_hash_ptr, contract_package_hash_size)?; + let entry_points: EntryPoints = + self.t_from_mem(entry_points_ptr, entry_points_size)?; + let named_keys: NamedKeys = self.t_from_mem(named_keys_ptr, named_keys_size)?; + let ret = self.add_contract_version( + contract_package_hash, + entry_points, + named_keys, + output_ptr, + output_size as usize, + bytes_written_ptr, + version_ptr, + )?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::DisableContractVersion => { + // args(0) = pointer to package hash in wasm memory + // args(1) = size of package hash in wasm memory + // args(2) = pointer to contract hash in wasm memory + // args(3) = size of contract hash in wasm memory + let (package_key_ptr, package_key_size, contract_hash_ptr, contract_hash_size) = + Args::parse(args)?; + + let contract_package_hash = self.t_from_mem(package_key_ptr, package_key_size)?; + let contract_hash = self.t_from_mem(contract_hash_ptr, contract_hash_size)?; + + let result = self.disable_contract_version(contract_package_hash, contract_hash)?; + + Ok(Some(RuntimeValue::I32(api_error::i32_from(result)))) + } + + FunctionIndex::CallContractFuncIndex => { + // args(0) = pointer to contract hash where contract is at in global state + // args(1) = size of contract hash + // args(2) = pointer to entry point + // args(3) = size of entry point + // args(4) = pointer to function arguments in Wasm memory + // args(5) = size of arguments + // args(6) = pointer to result size (output) + let ( + contract_hash_ptr, + contract_hash_size, + entry_point_name_ptr, + entry_point_name_size, + args_ptr, + args_size, + result_size_ptr, + ): (_, _, _, u32, _, u32, _) = Args::parse(args)?; + scoped_instrumenter + .add_property("entry_point_name_size", entry_point_name_size.to_string()); + scoped_instrumenter.add_property("args_size", args_size.to_string()); + + let contract_hash: ContractHash = + self.t_from_mem(contract_hash_ptr, contract_hash_size)?; + let entry_point_name: String = + self.t_from_mem(entry_point_name_ptr, entry_point_name_size)?; + let args_bytes: Vec = { + let args_size: u32 = args_size; + self.bytes_from_mem(args_ptr, args_size as usize)? + }; + + let ret = self.call_contract_host_buffer( + contract_hash, + &entry_point_name, + args_bytes, + result_size_ptr, + &mut scoped_instrumenter, + )?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::CallVersionedContract => { + // args(0) = pointer to contract_package_hash where contract is at in global state + // args(1) = size of contract_package_hash + // args(2) = pointer to contract version in wasm memory + // args(3) = size of contract version in wasm memory + // args(4) = pointer to method name in wasm memory + // args(5) = size of method name in wasm memory + // args(6) = pointer to function arguments in Wasm memory + // args(7) = size of arguments + // args(8) = pointer to result size (output) + let ( + contract_package_hash_ptr, + contract_package_hash_size, + contract_version_ptr, + contract_package_size, + entry_point_name_ptr, + entry_point_name_size, + args_ptr, + args_size, + result_size_ptr, + ): (_, _, _, _, _, u32, _, u32, _) = Args::parse(args)?; + + scoped_instrumenter + .add_property("entry_point_name_size", entry_point_name_size.to_string()); + scoped_instrumenter.add_property("args_size", args_size.to_string()); + + let contract_package_hash: ContractPackageHash = + self.t_from_mem(contract_package_hash_ptr, contract_package_hash_size)?; + let contract_version: Option = + self.t_from_mem(contract_version_ptr, contract_package_size)?; + let entry_point_name: String = + self.t_from_mem(entry_point_name_ptr, entry_point_name_size)?; + let args_bytes: Vec = { + let args_size: u32 = args_size; + self.bytes_from_mem(args_ptr, args_size as usize)? + }; + + let ret = self.call_versioned_contract_host_buffer( + contract_package_hash, + contract_version, + entry_point_name, + args_bytes, + result_size_ptr, + &mut scoped_instrumenter, + )?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + #[cfg(feature = "test-support")] + FunctionIndex::PrintIndex => { + let (text_ptr, text_size): (_, u32) = Args::parse(args)?; + scoped_instrumenter.add_property("text_size", text_size); + self.print(text_ptr, text_size)?; + Ok(None) + } + + FunctionIndex::GetRuntimeArgsizeIndex => { + // args(0) = pointer to name of host runtime arg to load + // args(1) = size of name of the host runtime arg + // args(2) = pointer to a argument size (output) + let (name_ptr, name_size, size_ptr): (u32, u32, u32) = Args::parse(args)?; + scoped_instrumenter.add_property("name_size", name_size.to_string()); + let ret = self.get_named_arg_size(name_ptr, name_size as usize, size_ptr)?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::GetRuntimeArgIndex => { + // args(0) = pointer to serialized argument name + // args(1) = size of serialized argument name + // args(2) = pointer to output pointer where host will write argument bytes + // args(3) = size of available data under output pointer + let (name_ptr, name_size, dest_ptr, dest_size): (u32, u32, u32, u32) = + Args::parse(args)?; + scoped_instrumenter.add_property("name_size", name_size.to_string()); + scoped_instrumenter.add_property("dest_size", dest_size.to_string()); + let ret = + self.get_named_arg(name_ptr, name_size as usize, dest_ptr, dest_size as usize)?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::RemoveContractUserGroupIndex => { + // args(0) = pointer to package key in wasm memory + // args(1) = size of package key in wasm memory + // args(2) = pointer to serialized group label + // args(3) = size of serialized group label + let (package_key_ptr, package_key_size, label_ptr, label_size): (_, _, _, u32) = + Args::parse(args)?; + scoped_instrumenter.add_property("label_size", label_size.to_string()); + let package_key = self.t_from_mem(package_key_ptr, package_key_size)?; + let label: Group = self.t_from_mem(label_ptr, label_size)?; + + let ret = self.remove_contract_user_group(package_key, label)?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::ExtendContractUserGroupURefsIndex => { + // args(0) = pointer to package key in wasm memory + // args(1) = size of package key in wasm memory + // args(2) = pointer to label name + // args(3) = label size bytes + // args(4) = output of size value of host bytes data + let (package_ptr, package_size, label_ptr, label_size, value_size_ptr): ( + _, + _, + _, + u32, + _, + ) = Args::parse(args)?; + scoped_instrumenter.add_property("label_size", label_size.to_string()); + let ret = self.provision_contract_user_group_uref( + package_ptr, + package_size, + label_ptr, + label_size, + value_size_ptr, + )?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + + FunctionIndex::RemoveContractUserGroupURefsIndex => { + // args(0) = pointer to package key in wasm memory + // args(1) = size of package key in wasm memory + // args(2) = pointer to label name + // args(3) = label size bytes + // args(4) = pointer to urefs + // args(5) = size of urefs pointer + let (package_ptr, package_size, label_ptr, label_size, urefs_ptr, urefs_size): ( + _, + _, + _, + u32, + _, + u32, + ) = Args::parse(args)?; + scoped_instrumenter.add_property("label_size", label_size.to_string()); + scoped_instrumenter.add_property("urefs_size", urefs_size.to_string()); + let ret = self.remove_contract_user_group_urefs( + package_ptr, + package_size, + label_ptr, + label_size, + urefs_ptr, + urefs_size, + )?; + Ok(Some(RuntimeValue::I32(api_error::i32_from(ret)))) + } + } + } +} diff --git a/node/src/contract_core/runtime/mint_internal.rs b/node/src/contract_core/runtime/mint_internal.rs new file mode 100644 index 0000000000..6b2cf6082f --- /dev/null +++ b/node/src/contract_core/runtime/mint_internal.rs @@ -0,0 +1,92 @@ +use crate::contract_shared::stored_value::StoredValue; +use crate::contract_storage::global_state::StateReader; +use types::mint::{Mint, RuntimeProvider, StorageProvider}; +use types::{ + account::AccountHash, + bytesrepr::{FromBytes, ToBytes}, + system_contract_errors::mint::Error, + CLTyped, CLValue, Key, URef, +}; + +use crate::contract_core::{execution, runtime_context::RuntimeContext}; + +impl<'a, R> RuntimeProvider for RuntimeContext<'a, R> +where + R: StateReader, + R::Error: Into, +{ + fn get_caller(&self) -> AccountHash { + self.get_caller() + } + + fn put_key(&mut self, name: &str, key: Key) { + // TODO: update RuntimeProvider to better handle errors + self.put_key(name.to_string(), key).expect("should put key") + } +} + +// TODO: update Mint + StorageProvider to better handle errors +impl<'a, R> StorageProvider for RuntimeContext<'a, R> +where + R: StateReader, + R::Error: Into, +{ + fn new_uref(&mut self, init: T) -> URef { + let cl_value: CLValue = CLValue::from_t(init).expect("should convert value"); + self.new_uref(StoredValue::CLValue(cl_value)) + .expect("should create new uref") + } + + fn write_local(&mut self, key: K, value: V) { + let key_bytes = key.to_bytes().expect("should serialize"); + let cl_value = CLValue::from_t(value).expect("should convert"); + self.write_ls(&key_bytes, cl_value) + .expect("should write local state") + } + + fn read_local( + &mut self, + key: &K, + ) -> Result, Error> { + let key_bytes = key.to_bytes().expect("should serialize"); + let maybe_value = self.read_ls(&key_bytes).map_err(|_| Error::Storage)?; + match maybe_value { + Some(value) => { + let value = CLValue::into_t(value).unwrap(); + Ok(Some(value)) + } + None => Ok(None), + } + } + + fn read(&mut self, uref: URef) -> Result, Error> { + let maybe_value = self.read_gs(&Key::URef(uref)).map_err(|_| Error::Storage)?; + match maybe_value { + Some(StoredValue::CLValue(value)) => { + let value = CLValue::into_t(value).unwrap(); + Ok(Some(value)) + } + Some(error) => panic!("should have received value: {:?}", error), + None => Ok(None), + } + } + + fn write(&mut self, uref: URef, value: T) -> Result<(), Error> { + let cl_value = CLValue::from_t(value).expect("should convert"); + self.write_gs(Key::URef(uref), StoredValue::CLValue(cl_value)) + .map_err(|_| Error::Storage) + } + + fn add(&mut self, uref: URef, value: T) -> Result<(), Error> { + let cl_value = CLValue::from_t(value).expect("should convert"); + self.add_gs(Key::URef(uref), StoredValue::CLValue(cl_value)) + .map_err(|_| Error::Storage) + } +} + +impl<'a, R> Mint for RuntimeContext<'a, R> +where + R: StateReader, + R::Error: Into, +{ +} diff --git a/node/src/contract_core/runtime/mod.rs b/node/src/contract_core/runtime/mod.rs new file mode 100644 index 0000000000..336bc833cd --- /dev/null +++ b/node/src/contract_core/runtime/mod.rs @@ -0,0 +1,3515 @@ +mod args; +mod externals; +mod mint_internal; +mod proof_of_stake_internal; +mod scoped_instrumenter; +mod standard_payment_internal; + +use std::{ + cmp, + collections::{BTreeMap, BTreeSet, HashMap, HashSet}, + convert::TryFrom, + iter::IntoIterator, +}; + +use itertools::Itertools; +use parity_wasm::elements::Module; +use wasmi::{ImportsBuilder, MemoryRef, ModuleInstance, ModuleRef, Trap, TrapKind}; + +use crate::contract_shared::{account::Account, gas::Gas, stored_value::StoredValue}; +use crate::contract_storage::{global_state::StateReader, protocol_data::ProtocolData}; +use types::mint::Mint; +use types::proof_of_stake::ProofOfStake; +use types::standard_payment::StandardPayment; +use types::{ + account::{AccountHash, ActionType, Weight}, + bytesrepr::{self, FromBytes, ToBytes}, + contracts::{ + self, Contract, ContractPackage, EntryPoint, EntryPointAccess, EntryPoints, Group, + }, + runtime_args, system_contract_errors, + system_contract_errors::mint, + AccessRights, ApiError, CLType, CLTyped, CLValue, ContractHash, ContractPackageHash, + ContractVersionKey, ContractWasm, EntryPointType, Key, ProtocolVersion, RuntimeArgs, + SystemContractType, TransferResult, TransferredTo, URef, U128, U256, U512, +}; + +use crate::contract_core::{ + engine_state::{system_contract_cache::SystemContractCache, EngineConfig}, + execution::Error, + resolvers::{create_module_resolver, memory_resolver::MemoryResolver}, + runtime_context::{self, RuntimeContext}, + Address, +}; +use contracts::{ContractVersion, ContractVersions, DisabledVersions, Groups, NamedKeys}; +use scoped_instrumenter::ScopedInstrumenter; + +pub struct Runtime<'a, R> { + system_contract_cache: SystemContractCache, + config: EngineConfig, + memory: MemoryRef, + module: Module, + host_buffer: Option, + context: RuntimeContext<'a, R>, +} + +/// Rename function called `name` in the `module` to `call`. +/// wasmi's entrypoint for a contracts is a function called `call`, +/// so we have to rename function before storing it in the GlobalState. +pub fn rename_export_to_call(module: &mut Module, name: String) { + let main_export = module + .export_section_mut() + .unwrap() + .entries_mut() + .iter_mut() + .find(|e| e.field() == name) + .unwrap() + .field_mut(); + main_export.clear(); + main_export.push_str("call"); +} + +pub fn instance_and_memory( + parity_module: Module, + protocol_version: ProtocolVersion, +) -> Result<(ModuleRef, MemoryRef), Error> { + let module = wasmi::Module::from_parity_wasm_module(parity_module)?; + let resolver = create_module_resolver(protocol_version)?; + let mut imports = ImportsBuilder::new(); + imports.push_resolver("env", &resolver); + let not_started_module = ModuleInstance::new(&module, &imports)?; + if not_started_module.has_start() { + return Err(Error::UnsupportedWasmStart); + } + let instance = not_started_module.not_started_instance().clone(); + let memory = resolver.memory_ref()?; + Ok((instance, memory)) +} + +/// Turns `key` into a `([u8; 32], AccessRights)` tuple. +/// Returns None if `key` is not `Key::URef` as it wouldn't have `AccessRights` +/// associated with it. Helper function for creating `named_keys` associating +/// addresses and corresponding `AccessRights`. +pub fn key_to_tuple(key: Key) -> Option<([u8; 32], AccessRights)> { + match key { + Key::URef(uref) => Some((uref.addr(), uref.access_rights())), + Key::Account(_) => None, + Key::Hash(_) => None, + } +} + +/// Groups a collection of urefs by their addresses and accumulates access +/// rights per key +pub fn extract_access_rights_from_urefs>( + input: I, +) -> HashMap> { + input + .into_iter() + .map(|uref: URef| (uref.addr(), uref.access_rights())) + .group_by(|(key, _)| *key) + .into_iter() + .map(|(key, group)| { + ( + key, + group.map(|(_, x)| x).collect::>(), + ) + }) + .collect() +} + +/// Groups a collection of keys by their address and accumulates access rights +/// per key. +pub fn extract_access_rights_from_keys>( + input: I, +) -> HashMap> { + input + .into_iter() + .map(key_to_tuple) + .flatten() + .group_by(|(key, _)| *key) + .into_iter() + .map(|(key, group)| { + ( + key, + group.map(|(_, x)| x).collect::>(), + ) + }) + .collect() +} + +#[allow(clippy::cognitive_complexity)] +fn extract_urefs(cl_value: &CLValue) -> Result, Error> { + match cl_value.cl_type() { + CLType::Bool + | CLType::I32 + | CLType::I64 + | CLType::U8 + | CLType::U32 + | CLType::U64 + | CLType::U128 + | CLType::U256 + | CLType::U512 + | CLType::Unit + | CLType::String + | CLType::Any => Ok(vec![]), + CLType::Option(ty) => match **ty { + CLType::URef => { + let opt: Option = cl_value.to_owned().into_t()?; + Ok(opt.into_iter().collect()) + } + CLType::Key => { + let opt: Option = cl_value.to_owned().into_t()?; + Ok(opt.into_iter().flat_map(Key::into_uref).collect()) + } + _ => Ok(vec![]), + }, + CLType::List(ty) => match **ty { + CLType::URef => Ok(cl_value.to_owned().into_t()?), + CLType::Key => { + let keys: Vec = cl_value.to_owned().into_t()?; + Ok(keys.into_iter().filter_map(Key::into_uref).collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 1) => match **ty { + CLType::URef => { + let arr: [URef; 1] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 1] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 2) => match **ty { + CLType::URef => { + let arr: [URef; 2] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 2] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 3) => match **ty { + CLType::URef => { + let arr: [URef; 3] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 3] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 4) => match **ty { + CLType::URef => { + let arr: [URef; 4] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 4] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 5) => match **ty { + CLType::URef => { + let arr: [URef; 5] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 5] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 6) => match **ty { + CLType::URef => { + let arr: [URef; 6] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 6] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 7) => match **ty { + CLType::URef => { + let arr: [URef; 7] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 7] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 8) => match **ty { + CLType::URef => { + let arr: [URef; 8] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 8] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 9) => match **ty { + CLType::URef => { + let arr: [URef; 9] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 9] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 10) => match **ty { + CLType::URef => { + let arr: [URef; 10] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 10] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 11) => match **ty { + CLType::URef => { + let arr: [URef; 11] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 11] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 12) => match **ty { + CLType::URef => { + let arr: [URef; 12] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 12] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 13) => match **ty { + CLType::URef => { + let arr: [URef; 13] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 13] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 14) => match **ty { + CLType::URef => { + let arr: [URef; 14] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 14] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 15) => match **ty { + CLType::URef => { + let arr: [URef; 15] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 15] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 16) => match **ty { + CLType::URef => { + let arr: [URef; 16] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 16] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 17) => match **ty { + CLType::URef => { + let arr: [URef; 17] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 17] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 18) => match **ty { + CLType::URef => { + let arr: [URef; 18] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 18] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 19) => match **ty { + CLType::URef => { + let arr: [URef; 19] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 19] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 20) => match **ty { + CLType::URef => { + let arr: [URef; 20] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 20] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 21) => match **ty { + CLType::URef => { + let arr: [URef; 21] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 21] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 22) => match **ty { + CLType::URef => { + let arr: [URef; 22] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 22] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 23) => match **ty { + CLType::URef => { + let arr: [URef; 23] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 23] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 24) => match **ty { + CLType::URef => { + let arr: [URef; 24] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 24] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 25) => match **ty { + CLType::URef => { + let arr: [URef; 25] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 25] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 26) => match **ty { + CLType::URef => { + let arr: [URef; 26] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 26] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 27) => match **ty { + CLType::URef => { + let arr: [URef; 27] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 27] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 28) => match **ty { + CLType::URef => { + let arr: [URef; 28] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 28] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 29) => match **ty { + CLType::URef => { + let arr: [URef; 29] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 29] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 30) => match **ty { + CLType::URef => { + let arr: [URef; 30] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 30] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 31) => match **ty { + CLType::URef => { + let arr: [URef; 31] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 31] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 32) => match **ty { + CLType::URef => { + let arr: [URef; 32] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 32] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 64) => match **ty { + CLType::URef => { + let arr: [URef; 64] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 64] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 128) => match **ty { + CLType::URef => { + let arr: [URef; 128] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 128] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 256) => match **ty { + CLType::URef => { + let arr: [URef; 256] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 256] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(ty, 512) => match **ty { + CLType::URef => { + let arr: [URef; 512] = cl_value.to_owned().into_t()?; + Ok(arr.to_vec()) + } + CLType::Key => { + let arr: [Key; 512] = cl_value.to_owned().into_t()?; + Ok(arr.iter().filter_map(Key::as_uref).cloned().collect()) + } + _ => Ok(vec![]), + }, + CLType::FixedList(_ty, _) => Ok(vec![]), + CLType::Result { ok, err } => match (&**ok, &**err) { + (CLType::URef, CLType::Bool) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(_) => Ok(vec![]), + } + } + (CLType::URef, CLType::I32) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(_) => Ok(vec![]), + } + } + (CLType::URef, CLType::I64) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(_) => Ok(vec![]), + } + } + (CLType::URef, CLType::U8) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(_) => Ok(vec![]), + } + } + (CLType::URef, CLType::U32) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(_) => Ok(vec![]), + } + } + (CLType::URef, CLType::U64) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(_) => Ok(vec![]), + } + } + (CLType::URef, CLType::U128) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(_) => Ok(vec![]), + } + } + (CLType::URef, CLType::U256) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(_) => Ok(vec![]), + } + } + (CLType::URef, CLType::U512) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(_) => Ok(vec![]), + } + } + (CLType::URef, CLType::Unit) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(_) => Ok(vec![]), + } + } + (CLType::URef, CLType::String) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(_) => Ok(vec![]), + } + } + (CLType::URef, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::URef, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(uref) => Ok(vec![uref]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::Key, CLType::Bool) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(_) => Ok(vec![]), + } + } + (CLType::Key, CLType::I32) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(_) => Ok(vec![]), + } + } + (CLType::Key, CLType::I64) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(_) => Ok(vec![]), + } + } + (CLType::Key, CLType::U8) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(_) => Ok(vec![]), + } + } + (CLType::Key, CLType::U32) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(_) => Ok(vec![]), + } + } + (CLType::Key, CLType::U64) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(_) => Ok(vec![]), + } + } + (CLType::Key, CLType::U128) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(_) => Ok(vec![]), + } + } + (CLType::Key, CLType::U256) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(_) => Ok(vec![]), + } + } + (CLType::Key, CLType::U512) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(_) => Ok(vec![]), + } + } + (CLType::Key, CLType::Unit) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(_) => Ok(vec![]), + } + } + (CLType::Key, CLType::String) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(_) => Ok(vec![]), + } + } + (CLType::Key, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::Key, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(key) => Ok(key.into_uref().into_iter().collect()), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::Bool, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::I32, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::I64, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::U8, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::U32, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::U64, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::U128, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::U256, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::U512, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::Unit, CLType::URef) => { + let res: Result<(), URef> = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::String, CLType::URef) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(uref) => Ok(vec![uref]), + } + } + (CLType::Bool, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::I32, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::I64, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::U8, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::U32, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::U64, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::U128, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::U256, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::U512, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::Unit, CLType::Key) => { + let res: Result<(), Key> = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (CLType::String, CLType::Key) => { + let res: Result = cl_value.to_owned().into_t()?; + match res { + Ok(_) => Ok(vec![]), + Err(key) => Ok(key.into_uref().into_iter().collect()), + } + } + (_, _) => Ok(vec![]), + }, + CLType::Map { key, value } => match (&**key, &**value) { + (CLType::URef, CLType::Bool) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().collect()) + } + (CLType::URef, CLType::I32) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().collect()) + } + (CLType::URef, CLType::I64) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().collect()) + } + (CLType::URef, CLType::U8) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().collect()) + } + (CLType::URef, CLType::U32) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().collect()) + } + (CLType::URef, CLType::U64) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().collect()) + } + (CLType::URef, CLType::U128) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().collect()) + } + (CLType::URef, CLType::U256) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().collect()) + } + (CLType::URef, CLType::U512) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().collect()) + } + (CLType::URef, CLType::Unit) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().collect()) + } + (CLType::URef, CLType::String) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().collect()) + } + (CLType::URef, CLType::Key) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map + .keys() + .cloned() + .chain(map.values().cloned().filter_map(Key::into_uref)) + .collect()) + } + (CLType::URef, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().chain(map.values().cloned()).collect()) + } + (CLType::Key, CLType::Bool) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Key, CLType::I32) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Key, CLType::I64) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Key, CLType::U8) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Key, CLType::U32) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Key, CLType::U64) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Key, CLType::U128) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Key, CLType::U256) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Key, CLType::U512) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Key, CLType::Unit) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Key, CLType::String) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.keys().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Key, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map + .keys() + .cloned() + .filter_map(Key::into_uref) + .chain(map.values().cloned()) + .collect()) + } + (CLType::Key, CLType::Key) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map + .keys() + .cloned() + .filter_map(Key::into_uref) + .chain(map.values().cloned().filter_map(Key::into_uref)) + .collect()) + } + (CLType::Bool, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().collect()) + } + (CLType::I32, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().collect()) + } + (CLType::I64, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().collect()) + } + (CLType::U8, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().collect()) + } + (CLType::U32, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().collect()) + } + (CLType::U64, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().collect()) + } + (CLType::U128, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().collect()) + } + (CLType::U256, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().collect()) + } + (CLType::U512, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().collect()) + } + (CLType::Unit, CLType::URef) => { + let map: BTreeMap<(), URef> = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().collect()) + } + (CLType::String, CLType::URef) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().collect()) + } + (CLType::Bool, CLType::Key) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::I32, CLType::Key) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::I64, CLType::Key) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::U8, CLType::Key) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::U32, CLType::Key) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::U64, CLType::Key) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::U128, CLType::Key) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::U256, CLType::Key) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::U512, CLType::Key) => { + let map: BTreeMap = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::Unit, CLType::Key) => { + let map: BTreeMap<(), Key> = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().filter_map(Key::into_uref).collect()) + } + (CLType::String, CLType::Key) => { + let map: NamedKeys = cl_value.to_owned().into_t()?; + Ok(map.values().cloned().filter_map(Key::into_uref).collect()) + } + (_, _) => Ok(vec![]), + }, + CLType::Tuple1([ty]) => match **ty { + CLType::URef => { + let val: (URef,) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + CLType::Key => { + let val: (Key,) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + _ => Ok(vec![]), + }, + CLType::Tuple2([ty1, ty2]) => match (&**ty1, &**ty2) { + (CLType::URef, CLType::Bool) => { + let val: (URef, bool) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + (CLType::URef, CLType::I32) => { + let val: (URef, i32) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + (CLType::URef, CLType::I64) => { + let val: (URef, i64) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + (CLType::URef, CLType::U8) => { + let val: (URef, u8) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + (CLType::URef, CLType::U32) => { + let val: (URef, u32) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + (CLType::URef, CLType::U64) => { + let val: (URef, u64) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + (CLType::URef, CLType::U128) => { + let val: (URef, U128) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + (CLType::URef, CLType::U256) => { + let val: (URef, U256) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + (CLType::URef, CLType::U512) => { + let val: (URef, U512) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + (CLType::URef, CLType::Unit) => { + let val: (URef, ()) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + (CLType::URef, CLType::String) => { + let val: (URef, String) = cl_value.to_owned().into_t()?; + Ok(vec![val.0]) + } + (CLType::URef, CLType::Key) => { + let val: (URef, Key) = cl_value.to_owned().into_t()?; + let mut res = vec![val.0]; + res.extend(val.1.into_uref().into_iter()); + Ok(res) + } + (CLType::URef, CLType::URef) => { + let val: (URef, URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.0, val.1]) + } + (CLType::Key, CLType::Bool) => { + let val: (Key, bool) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + (CLType::Key, CLType::I32) => { + let val: (Key, i32) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + (CLType::Key, CLType::I64) => { + let val: (Key, i64) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + (CLType::Key, CLType::U8) => { + let val: (Key, u8) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + (CLType::Key, CLType::U32) => { + let val: (Key, u32) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + (CLType::Key, CLType::U64) => { + let val: (Key, u64) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + (CLType::Key, CLType::U128) => { + let val: (Key, U128) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + (CLType::Key, CLType::U256) => { + let val: (Key, U256) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + (CLType::Key, CLType::U512) => { + let val: (Key, U512) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + (CLType::Key, CLType::Unit) => { + let val: (Key, ()) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + (CLType::Key, CLType::String) => { + let val: (Key, String) = cl_value.to_owned().into_t()?; + Ok(val.0.into_uref().into_iter().collect()) + } + (CLType::Key, CLType::URef) => { + let val: (Key, URef) = cl_value.to_owned().into_t()?; + let mut res: Vec = val.0.into_uref().into_iter().collect(); + res.push(val.1); + Ok(res) + } + (CLType::Key, CLType::Key) => { + let val: (Key, Key) = cl_value.to_owned().into_t()?; + Ok(val + .0 + .into_uref() + .into_iter() + .chain(val.1.into_uref().into_iter()) + .collect()) + } + (CLType::Bool, CLType::URef) => { + let val: (bool, URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.1]) + } + (CLType::I32, CLType::URef) => { + let val: (i32, URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.1]) + } + (CLType::I64, CLType::URef) => { + let val: (i64, URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.1]) + } + (CLType::U8, CLType::URef) => { + let val: (u8, URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.1]) + } + (CLType::U32, CLType::URef) => { + let val: (u32, URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.1]) + } + (CLType::U64, CLType::URef) => { + let val: (u64, URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.1]) + } + (CLType::U128, CLType::URef) => { + let val: (U128, URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.1]) + } + (CLType::U256, CLType::URef) => { + let val: (U256, URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.1]) + } + (CLType::U512, CLType::URef) => { + let val: (U512, URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.1]) + } + (CLType::Unit, CLType::URef) => { + let val: ((), URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.1]) + } + (CLType::String, CLType::URef) => { + let val: (String, URef) = cl_value.to_owned().into_t()?; + Ok(vec![val.1]) + } + (CLType::Bool, CLType::Key) => { + let val: (bool, Key) = cl_value.to_owned().into_t()?; + Ok(val.1.into_uref().into_iter().collect()) + } + (CLType::I32, CLType::Key) => { + let val: (i32, Key) = cl_value.to_owned().into_t()?; + Ok(val.1.into_uref().into_iter().collect()) + } + (CLType::I64, CLType::Key) => { + let val: (i64, Key) = cl_value.to_owned().into_t()?; + Ok(val.1.into_uref().into_iter().collect()) + } + (CLType::U8, CLType::Key) => { + let val: (u8, Key) = cl_value.to_owned().into_t()?; + Ok(val.1.into_uref().into_iter().collect()) + } + (CLType::U32, CLType::Key) => { + let val: (u32, Key) = cl_value.to_owned().into_t()?; + Ok(val.1.into_uref().into_iter().collect()) + } + (CLType::U64, CLType::Key) => { + let val: (u64, Key) = cl_value.to_owned().into_t()?; + Ok(val.1.into_uref().into_iter().collect()) + } + (CLType::U128, CLType::Key) => { + let val: (U128, Key) = cl_value.to_owned().into_t()?; + Ok(val.1.into_uref().into_iter().collect()) + } + (CLType::U256, CLType::Key) => { + let val: (U256, Key) = cl_value.to_owned().into_t()?; + Ok(val.1.into_uref().into_iter().collect()) + } + (CLType::U512, CLType::Key) => { + let val: (U512, Key) = cl_value.to_owned().into_t()?; + Ok(val.1.into_uref().into_iter().collect()) + } + (CLType::Unit, CLType::Key) => { + let val: ((), Key) = cl_value.to_owned().into_t()?; + Ok(val.1.into_uref().into_iter().collect()) + } + (CLType::String, CLType::Key) => { + let val: (String, Key) = cl_value.to_owned().into_t()?; + Ok(val.1.into_uref().into_iter().collect()) + } + (_, _) => Ok(vec![]), + }, + // TODO: nested matches for Tuple3? + CLType::Tuple3(_) => Ok(vec![]), + CLType::Key => { + let key: Key = cl_value.to_owned().into_t()?; // TODO: optimize? + Ok(key.into_uref().into_iter().collect()) + } + CLType::URef => { + let uref: URef = cl_value.to_owned().into_t()?; // TODO: optimize? + Ok(vec![uref]) + } + } +} + +impl<'a, R> Runtime<'a, R> +where + R: StateReader, + R::Error: Into, +{ + pub fn new( + config: EngineConfig, + system_contract_cache: SystemContractCache, + memory: MemoryRef, + module: Module, + context: RuntimeContext<'a, R>, + ) -> Self { + Runtime { + config, + system_contract_cache, + memory, + module, + host_buffer: None, + context, + } + } + + pub fn memory(&self) -> &MemoryRef { + &self.memory + } + + pub fn module(&self) -> &Module { + &self.module + } + + pub fn context(&self) -> &RuntimeContext<'a, R> { + &self.context + } + + pub fn protocol_data(&self) -> ProtocolData { + self.context.protocol_data() + } + + /// Charge specified amount of gas + /// + /// Returns false if gas limit exceeded and true if not. + /// Intuition about the return value sense is to answer the question 'are we + /// allowed to continue?' + fn charge_gas(&mut self, amount: Gas) -> bool { + let prev = self.context.gas_counter(); + match prev.checked_add(amount) { + // gas charge overflow protection + None => false, + Some(val) if val > self.context.gas_limit() => false, + Some(val) => { + self.context.set_gas_counter(val); + true + } + } + } + + fn gas(&mut self, amount: Gas) -> Result<(), Trap> { + if self.charge_gas(amount) { + Ok(()) + } else { + Err(Error::GasLimit.into()) + } + } + + fn bytes_from_mem(&self, ptr: u32, size: usize) -> Result, Error> { + self.memory.get(ptr, size).map_err(Into::into) + } + + fn t_from_mem(&self, ptr: u32, size: u32) -> Result { + let bytes = self.bytes_from_mem(ptr, size as usize)?; + bytesrepr::deserialize(bytes).map_err(Into::into) + } + + /// Reads key (defined as `key_ptr` and `key_size` tuple) from Wasm memory. + fn key_from_mem(&mut self, key_ptr: u32, key_size: u32) -> Result { + let bytes = self.bytes_from_mem(key_ptr, key_size as usize)?; + bytesrepr::deserialize(bytes).map_err(Into::into) + } + + /// Reads `CLValue` (defined as `cl_value_ptr` and `cl_value_size` tuple) from Wasm memory. + fn cl_value_from_mem( + &mut self, + cl_value_ptr: u32, + cl_value_size: u32, + ) -> Result { + let bytes = self.bytes_from_mem(cl_value_ptr, cl_value_size as usize)?; + bytesrepr::deserialize(bytes).map_err(Into::into) + } + + fn string_from_mem(&self, ptr: u32, size: u32) -> Result { + let bytes = self.bytes_from_mem(ptr, size as usize)?; + bytesrepr::deserialize(bytes).map_err(|e| Error::BytesRepr(e).into()) + } + + fn get_module_from_entry_points( + &mut self, + entry_points: &EntryPoints, + ) -> Result, Error> { + let export_section = self + .module + .export_section() + .ok_or_else(|| Error::FunctionNotFound(String::from("Missing Export Section")))?; + + let entry_point_names: Vec<&str> = entry_points.keys().map(|s| s.as_str()).collect(); + + let maybe_missing_name: Option = entry_point_names + .iter() + .find(|name| { + export_section + .entries() + .iter() + .find(|export_entry| export_entry.field() == **name) + .is_none() + }) + .map(|s| String::from(*s)); + + if let Some(missing_name) = maybe_missing_name { + Err(Error::FunctionNotFound(missing_name)) + } else { + let mut module = self.module.clone(); + pwasm_utils::optimize(&mut module, entry_point_names).unwrap(); + parity_wasm::serialize(module).map_err(Error::ParityWasm) + } + } + + fn is_valid_uref(&mut self, uref_ptr: u32, uref_size: u32) -> Result { + let bytes = self.bytes_from_mem(uref_ptr, uref_size as usize)?; + let uref: URef = bytesrepr::deserialize(bytes).map_err(Error::BytesRepr)?; + Ok(self.context.validate_uref(&uref).is_ok()) + } + + /// Load the uref known by the given name into the Wasm memory + fn load_key( + &mut self, + name_ptr: u32, + name_size: u32, + output_ptr: u32, + output_size: usize, + bytes_written_ptr: u32, + ) -> Result, Trap> { + let name = self.string_from_mem(name_ptr, name_size)?; + + // Get a key and serialize it + let key = match self.context.named_keys_get(&name) { + Some(key) => key, + None => return Ok(Err(ApiError::MissingKey)), + }; + + let key_bytes = match key.to_bytes() { + Ok(bytes) => bytes, + Err(error) => return Ok(Err(error.into())), + }; + + // `output_size` has to be greater or equal to the actual length of serialized Key bytes + if output_size < key_bytes.len() { + return Ok(Err(ApiError::BufferTooSmall)); + } + + // Set serialized Key bytes into the output buffer + if let Err(error) = self.memory.set(output_ptr, &key_bytes) { + return Err(Error::Interpreter(error.into()).into()); + } + + // For all practical purposes following cast is assumed to be safe + let bytes_size = key_bytes.len() as u32; + let size_bytes = bytes_size.to_le_bytes(); // Wasm is little-endian + if let Err(error) = self.memory.set(bytes_written_ptr, &size_bytes) { + return Err(Error::Interpreter(error.into()).into()); + } + + Ok(Ok(())) + } + + fn has_key(&mut self, name_ptr: u32, name_size: u32) -> Result { + let name = self.string_from_mem(name_ptr, name_size)?; + if self.context.named_keys_contains_key(&name) { + Ok(0) + } else { + Ok(1) + } + } + + fn put_key( + &mut self, + name_ptr: u32, + name_size: u32, + key_ptr: u32, + key_size: u32, + ) -> Result<(), Trap> { + let name = self.string_from_mem(name_ptr, name_size)?; + let key = self.key_from_mem(key_ptr, key_size)?; + self.context.put_key(name, key).map_err(Into::into) + } + + fn remove_key(&mut self, name_ptr: u32, name_size: u32) -> Result<(), Trap> { + let name = self.string_from_mem(name_ptr, name_size)?; + self.context.remove_key(&name)?; + Ok(()) + } + + /// Writes runtime context's account main purse to [dest_ptr] in the Wasm memory. + fn get_main_purse(&mut self, dest_ptr: u32) -> Result<(), Trap> { + let purse = self.context.get_main_purse()?; + let purse_bytes = purse.into_bytes().map_err(Error::BytesRepr)?; + self.memory + .set(dest_ptr, &purse_bytes) + .map_err(|e| Error::Interpreter(e.into()).into()) + } + + /// Writes caller (deploy) account public key to [dest_ptr] in the Wasm + /// memory. + fn get_caller(&mut self, output_size: u32) -> Result, Trap> { + if !self.can_write_to_host_buffer() { + // Exit early if the host buffer is already occupied + return Ok(Err(ApiError::HostBufferFull)); + } + let value = CLValue::from_t(self.context.get_caller()).map_err(Error::CLValue)?; + let value_size = value.inner_bytes().len(); + + // Save serialized public key into host buffer + if let Err(error) = self.write_host_buffer(value) { + return Ok(Err(error)); + } + + // Write output + let output_size_bytes = value_size.to_le_bytes(); // Wasm is little-endian + if let Err(error) = self.memory.set(output_size, &output_size_bytes) { + return Err(Error::Interpreter(error.into()).into()); + } + Ok(Ok(())) + } + + /// Writes runtime context's phase to [dest_ptr] in the Wasm memory. + fn get_phase(&mut self, dest_ptr: u32) -> Result<(), Trap> { + let phase = self.context.phase(); + let bytes = phase.into_bytes().map_err(Error::BytesRepr)?; + self.memory + .set(dest_ptr, &bytes) + .map_err(|e| Error::Interpreter(e.into()).into()) + } + + /// Writes current blocktime to [dest_ptr] in Wasm memory. + fn get_blocktime(&self, dest_ptr: u32) -> Result<(), Trap> { + let blocktime = self + .context + .get_blocktime() + .into_bytes() + .map_err(Error::BytesRepr)?; + self.memory + .set(dest_ptr, &blocktime) + .map_err(|e| Error::Interpreter(e.into()).into()) + } + + /// Return some bytes from the memory and terminate the current `sub_call`. Note that the return + /// type is `Trap`, indicating that this function will always kill the current Wasm instance. + fn ret( + &mut self, + value_ptr: u32, + value_size: usize, + scoped_instrumenter: &mut ScopedInstrumenter, + ) -> Trap { + const UREF_COUNT: &str = "uref_count"; + self.host_buffer = None; + let mem_get = self + .memory + .get(value_ptr, value_size) + .map_err(|e| Error::Interpreter(e.into())); + match mem_get { + Ok(buf) => { + // Set the result field in the runtime and return the proper element of the `Error` + // enum indicating that the reason for exiting the module was a call to ret. + self.host_buffer = bytesrepr::deserialize(buf).ok(); + + let urefs = match &self.host_buffer { + Some(buf) => extract_urefs(buf), + None => Ok(vec![]), + }; + match urefs { + Ok(urefs) => { + scoped_instrumenter.add_property(UREF_COUNT, urefs.len()); + Error::Ret(urefs).into() + } + Err(e) => { + scoped_instrumenter.add_property(UREF_COUNT, 0); + e.into() + } + } + } + Err(e) => { + scoped_instrumenter.add_property(UREF_COUNT, 0); + e.into() + } + } + } + + pub fn is_mint(&self, key: Key) -> bool { + key.into_seed() == self.protocol_data().mint() + } + + pub fn is_proof_of_stake(&self, key: Key) -> bool { + key.into_seed() == self.protocol_data().proof_of_stake() + } + + fn get_named_argument( + args: &RuntimeArgs, + name: &str, + ) -> Result { + let arg: CLValue = args + .get(name) + .cloned() + .ok_or_else(|| Error::Revert(ApiError::MissingArgument))?; + arg.into_t() + .map_err(|_| Error::Revert(ApiError::InvalidArgument)) + } + + fn reverter>(error: T) -> Error { + let api_error: ApiError = error.into(); + Error::Revert(api_error) + } + + pub fn call_host_mint( + &mut self, + protocol_version: ProtocolVersion, + entry_point_name: &str, + named_keys: &mut NamedKeys, + runtime_args: &RuntimeArgs, + extra_keys: &[Key], + ) -> Result { + const METHOD_MINT: &str = "mint"; + const METHOD_CREATE: &str = "create"; + const METHOD_BALANCE: &str = "balance"; + const METHOD_TRANSFER: &str = "transfer"; + + let state = self.context.state(); + let access_rights = { + let mut keys: Vec = named_keys.values().cloned().collect(); + keys.extend(extra_keys); + keys.push(self.get_mint_contract().into()); + keys.push(self.get_pos_contract().into()); + extract_access_rights_from_keys(keys) + }; + let authorization_keys = self.context.authorization_keys().to_owned(); + let account = self.context.account(); + let base_key = self.protocol_data().mint().into(); + let blocktime = self.context.get_blocktime(); + let deploy_hash = self.context.get_deploy_hash(); + let gas_limit = self.context.gas_limit(); + let gas_counter = self.context.gas_counter(); + let hash_address_generator = self.context.hash_address_generator(); + let uref_address_generator = self.context.uref_address_generator(); + let correlation_id = self.context.correlation_id(); + let phase = self.context.phase(); + let protocol_data = self.context.protocol_data(); + + let mut mint_context = RuntimeContext::new( + state, + EntryPointType::Contract, + named_keys, + access_rights, + runtime_args.to_owned(), + authorization_keys, + account, + base_key, + blocktime, + deploy_hash, + gas_limit, + gas_counter, + hash_address_generator, + uref_address_generator, + protocol_version, + correlation_id, + phase, + protocol_data, + ); + + let ret: CLValue = match entry_point_name { + // Type: `fn mint(amount: U512) -> Result` + METHOD_MINT => { + let amount: U512 = Self::get_named_argument(&runtime_args, "amount")?; + let result: Result = mint_context.mint(amount); + CLValue::from_t(result)? + } + // Type: `fn create() -> URef` + METHOD_CREATE => { + let uref = mint_context.mint(U512::zero()).map_err(Self::reverter)?; + CLValue::from_t(uref).map_err(Self::reverter)? + } + // Type: `fn balance(purse: URef) -> Option` + METHOD_BALANCE => { + let uref: URef = Self::get_named_argument(&runtime_args, "purse")?; + let maybe_balance: Option = + mint_context.balance(uref).map_err(Self::reverter)?; + CLValue::from_t(maybe_balance).map_err(Self::reverter)? + } + // Type: `fn transfer(source: URef, target: URef, amount: U512) -> Result<(), Error>` + METHOD_TRANSFER => { + let source: URef = Self::get_named_argument(&runtime_args, "source")?; + let target: URef = Self::get_named_argument(&runtime_args, "target")?; + let amount: U512 = Self::get_named_argument(&runtime_args, "amount")?; + let result: Result<(), mint::Error> = mint_context.transfer(source, target, amount); + CLValue::from_t(result).map_err(Self::reverter)? + } + _ => CLValue::from_t(()).map_err(Self::reverter)?, + }; + let urefs = extract_urefs(&ret)?; + let access_rights = extract_access_rights_from_urefs(urefs); + self.context.access_rights_extend(access_rights); + Ok(ret) + } + + pub fn call_host_proof_of_stake( + &mut self, + protocol_version: ProtocolVersion, + entry_point_name: &str, + named_keys: &mut NamedKeys, + runtime_args: &RuntimeArgs, + extra_keys: &[Key], + ) -> Result { + const METHOD_BOND: &str = "bond"; + const METHOD_UNBOND: &str = "unbond"; + const METHOD_GET_PAYMENT_PURSE: &str = "get_payment_purse"; + const METHOD_SET_REFUND_PURSE: &str = "set_refund_purse"; + const METHOD_GET_REFUND_PURSE: &str = "get_refund_purse"; + const METHOD_FINALIZE_PAYMENT: &str = "finalize_payment"; + const ARG_AMOUNT: &str = "amount"; + const ARG_PURSE: &str = "purse"; + + let state = self.context.state(); + let access_rights = { + let mut keys: Vec = named_keys.values().cloned().collect(); + keys.extend(extra_keys); + keys.push(self.get_mint_contract().into()); + keys.push(self.get_pos_contract().into()); + extract_access_rights_from_keys(keys) + }; + let authorization_keys = self.context.authorization_keys().to_owned(); + let account = self.context.account(); + let base_key = self.protocol_data().proof_of_stake().into(); + let blocktime = self.context.get_blocktime(); + let deploy_hash = self.context.get_deploy_hash(); + let gas_limit = self.context.gas_limit(); + let gas_counter = self.context.gas_counter(); + let fn_store_id = self.context.hash_address_generator(); + let address_generator = self.context.uref_address_generator(); + let correlation_id = self.context.correlation_id(); + let phase = self.context.phase(); + let protocol_data = self.context.protocol_data(); + + let runtime_context = RuntimeContext::new( + state, + EntryPointType::Contract, + named_keys, + access_rights, + runtime_args.to_owned(), + authorization_keys, + account, + base_key, + blocktime, + deploy_hash, + gas_limit, + gas_counter, + fn_store_id, + address_generator, + protocol_version, + correlation_id, + phase, + protocol_data, + ); + + let mut runtime = Runtime::new( + self.config, + SystemContractCache::clone(&self.system_contract_cache), + self.memory.clone(), + self.module.clone(), + runtime_context, + ); + + let ret: CLValue = match entry_point_name { + METHOD_BOND => { + if !self.config.enable_bonding() { + let err = Error::Revert(ApiError::Unhandled); + return Err(err); + } + + let validator: AccountHash = runtime.context.get_caller(); + let amount: U512 = Self::get_named_argument(&runtime_args, ARG_AMOUNT)?; + let source_uref: URef = Self::get_named_argument(&runtime_args, ARG_PURSE)?; + runtime + .bond(validator, amount, source_uref) + .map_err(Self::reverter)?; + CLValue::from_t(()).map_err(Self::reverter)? + } + METHOD_UNBOND => { + if !self.config.enable_bonding() { + let err = Error::Revert(ApiError::Unhandled); + return Err(err); + } + + let validator: AccountHash = runtime.context.get_caller(); + let maybe_amount: Option = Self::get_named_argument(&runtime_args, "amount")?; + runtime + .unbond(validator, maybe_amount) + .map_err(Self::reverter)?; + CLValue::from_t(()).map_err(Self::reverter)? + } + METHOD_GET_PAYMENT_PURSE => { + let rights_controlled_purse = + runtime.get_payment_purse().map_err(Self::reverter)?; + CLValue::from_t(rights_controlled_purse).map_err(Self::reverter)? + } + METHOD_SET_REFUND_PURSE => { + let purse: URef = Self::get_named_argument(&runtime_args, "purse")?; + runtime.set_refund_purse(purse).map_err(Self::reverter)?; + CLValue::from_t(()).map_err(Self::reverter)? + } + METHOD_GET_REFUND_PURSE => { + let maybe_purse = runtime.get_refund_purse().map_err(Self::reverter)?; + CLValue::from_t(maybe_purse).map_err(Self::reverter)? + } + METHOD_FINALIZE_PAYMENT => { + let amount_spent: U512 = Self::get_named_argument(&runtime_args, "amount")?; + let account: AccountHash = Self::get_named_argument(&runtime_args, "account")?; + runtime + .finalize_payment(amount_spent, account) + .map_err(Self::reverter)?; + CLValue::from_t(()).map_err(Self::reverter)? + } + _ => CLValue::from_t(()).map_err(Self::reverter)?, + }; + let urefs = extract_urefs(&ret)?; + let access_rights = extract_access_rights_from_urefs(urefs); + self.context.access_rights_extend(access_rights); + Ok(ret) + } + + pub fn call_host_standard_payment(&mut self) -> Result<(), Error> { + let amount: U512 = Self::get_named_argument(&self.context.args(), "amount")?; + self.pay(amount).map_err(Self::reverter) + } + + /// Calls contract living under a `key`, with supplied `args`. + pub fn call_contract( + &mut self, + contract_hash: ContractHash, + entry_point_name: &str, + args: RuntimeArgs, + ) -> Result { + let key = contract_hash.into(); + let contract = match self.context.read_gs(&key)? { + Some(StoredValue::Contract(contract)) => contract, + Some(_) => { + return Err(Error::FunctionNotFound(format!( + "Value at {:?} is not a contract", + key + ))); + } + None => return Err(Error::KeyNotFound(key)), + }; + + let entry_point = contract + .entry_point(entry_point_name) + .cloned() + .ok_or_else(|| Error::NoSuchMethod(entry_point_name.to_owned()))?; + + let context_key = self.get_context_key_for_contract_call(contract_hash, &entry_point)?; + + self.execute_contract( + key, + context_key, + contract, + args, + entry_point, + self.context.protocol_version(), + ) + } + + /// Calls `version` of the contract living at `key`, invoking `method` with + /// supplied `args`. This function also checks the args conform with the + /// types given in the contract header. + pub fn call_versioned_contract( + &mut self, + contract_package_hash: ContractPackageHash, + contract_version: Option, + entry_point_name: String, + args: RuntimeArgs, + ) -> Result { + let key = contract_package_hash.into(); + + let contract_package = match self.context.read_gs(&key)? { + Some(StoredValue::ContractPackage(contract_package)) => contract_package, + Some(_) => { + return Err(Error::FunctionNotFound(format!( + "Value at {:?} is not a versioned contract", + contract_package_hash + ))); + } + None => return Err(Error::KeyNotFound(key)), + }; + + let contract_version_key = match contract_version { + Some(version) => { + ContractVersionKey::new(self.context.protocol_version().value().major, version) + } + None => match contract_package.current_contract_version() { + Some(v) => v, + None => return Err(Error::NoActiveContractVersions(contract_package_hash)), + }, + }; + + // Get contract entry point hash + let contract_hash = contract_package + .lookup_contract_hash(contract_version_key) + .cloned() + .ok_or_else(|| Error::InvalidContractVersion(contract_version_key))?; + + // Get contract data + let contract = match self.context.read_gs(&contract_hash.into())? { + Some(StoredValue::Contract(contract)) => contract, + Some(_) => { + return Err(Error::FunctionNotFound(format!( + "Value at {:?} is not a contract", + contract_package_hash + ))); + } + None => return Err(Error::KeyNotFound(key)), + }; + + let entry_point = contract + .entry_point(&entry_point_name) + .cloned() + .ok_or_else(|| Error::NoSuchMethod(entry_point_name.to_owned()))?; + + self.validate_entry_point_access(&contract_package, entry_point.access())?; + + for (expected, found) in entry_point + .args() + .iter() + .map(|a| a.cl_type()) + .cloned() + .zip(args.to_values().into_iter().map(|v| v.cl_type()).cloned()) + { + if expected != found { + return Err(Error::type_mismatch(expected, found)); + } + } + + let context_key = self.get_context_key_for_contract_call(contract_hash, &entry_point)?; + + self.execute_contract( + context_key, + context_key, + contract, + args, + entry_point, + self.context.protocol_version(), + ) + } + + fn get_context_key_for_contract_call( + &self, + contract_hash: ContractHash, + entry_point: &EntryPoint, + ) -> Result { + match entry_point.entry_point_type() { + EntryPointType::Session + if self.context.entry_point_type() == EntryPointType::Contract => + { + // Session code can't be called from Contract code for security reasons. + Err(Error::InvalidContext) + } + EntryPointType::Session => { + assert_eq!(self.context.entry_point_type(), EntryPointType::Session); + // Session code called from session reuses current base key + Ok(self.context.base_key()) + } + EntryPointType::Contract => Ok(contract_hash.into()), + } + } + + pub(crate) fn access_rights_extend( + &mut self, + access_rights: HashMap>, + ) { + self.context.access_rights_extend(access_rights) + } + + fn execute_contract( + &mut self, + key: Key, + base_key: Key, + contract: Contract, + args: RuntimeArgs, + entry_point: EntryPoint, + protocol_version: ProtocolVersion, + ) -> Result { + // Check for major version compatibility before calling + if !contract.is_compatible_protocol_version(protocol_version) { + return Err(Error::IncompatibleProtocolMajorVersion { + actual: contract.protocol_version().value().major, + expected: protocol_version.value().major, + }); + } + + // TODO: should we be using named_keys_mut() instead? + let mut named_keys = match entry_point.entry_point_type() { + EntryPointType::Session => self.context.account().named_keys().clone(), + EntryPointType::Contract => contract.named_keys().clone(), + }; + + let extra_keys = { + let mut extra_keys = vec![]; + // A loop is needed to be able to use the '?' operator + for arg in args.to_values() { + extra_keys.extend( + extract_urefs(arg)? + .into_iter() + .map(>::from), + ); + } + for key in &extra_keys { + self.context.validate_key(key)?; + } + + if !self.config.use_system_contracts() { + if self.is_mint(key) { + return self.call_host_mint( + self.context.protocol_version(), + entry_point.name(), + &mut named_keys, + &args, + &extra_keys, + ); + } else if self.is_proof_of_stake(key) { + return self.call_host_proof_of_stake( + self.context.protocol_version(), + entry_point.name(), + &mut named_keys, + &args, + &extra_keys, + ); + } + } + + extra_keys + }; + + let module = { + let maybe_module = self.system_contract_cache.get(key.into_seed()); + let wasm_key = contract.contract_wasm_key(); + + let contract_wasm: ContractWasm = match self.context.read_gs(&wasm_key)? { + Some(StoredValue::ContractWasm(contract_wasm)) => contract_wasm, + Some(_) => { + return Err(Error::FunctionNotFound(format!( + "Value at {:?} is not contract wasm", + key + ))); + } + None => return Err(Error::KeyNotFound(key)), + }; + match maybe_module { + Some(module) => module, + None => parity_wasm::deserialize_buffer(contract_wasm.bytes())?, + } + }; + + let entry_point_name = entry_point.name(); + + let (instance, memory) = instance_and_memory(module.clone(), protocol_version)?; + + let access_rights = { + let mut keys: Vec = named_keys.values().cloned().collect(); + keys.extend(extra_keys); + keys.push(self.get_mint_contract().into()); + keys.push(self.get_pos_contract().into()); + extract_access_rights_from_keys(keys) + }; + + let system_contract_cache = SystemContractCache::clone(&self.system_contract_cache); + + let config = self.config; + + let host_buffer = None; + + let context = RuntimeContext::new( + self.context.state(), + entry_point.entry_point_type(), + &mut named_keys, + access_rights, + args, + self.context.authorization_keys().clone(), + &self.context.account(), + base_key, + self.context.get_blocktime(), + self.context.get_deploy_hash(), + self.context.gas_limit(), + self.context.gas_counter(), + self.context.hash_address_generator(), + self.context.uref_address_generator(), + protocol_version, + self.context.correlation_id(), + self.context.phase(), + self.context.protocol_data(), + ); + + let mut runtime = Runtime { + system_contract_cache, + config, + memory, + module, + host_buffer, + context, + }; + + let result = instance.invoke_export(entry_point_name, &[], &mut runtime); + + // The `runtime`'s context was initialized with our counter from before the call and any gas + // charged by the sub-call was added to its counter - so let's copy the correct value of the + // counter from there to our counter + self.context.set_gas_counter(runtime.context.gas_counter()); + + let error = match result { + Err(error) => error, + // If `Ok` and the `host_buffer` is `None`, the contract's execution succeeded but did + // not explicitly call `runtime::ret()`. Treat as though the execution + // returned the unit type `()` as per Rust functions which don't specify a + // return value. + Ok(_) => { + if self.context.entry_point_type() == EntryPointType::Session + && runtime.context.entry_point_type() == EntryPointType::Session + { + // Overwrites parent's named keys with child's new named key but only when + // running session code + *self.context.named_keys_mut() = runtime.context.named_keys().clone(); + } + return Ok(runtime.take_host_buffer().unwrap_or(CLValue::from_t(())?)); + } + }; + + if let Some(host_error) = error.as_host_error() { + // If the "error" was in fact a trap caused by calling `ret` then + // this is normal operation and we should return the value captured + // in the Runtime result field. + let downcasted_error = host_error.downcast_ref::().unwrap(); + match downcasted_error { + Error::Ret(ref ret_urefs) => { + // insert extra urefs returned from call + let ret_urefs_map: HashMap> = + extract_access_rights_from_urefs(ret_urefs.clone()); + self.context.access_rights_extend(ret_urefs_map); + // if ret has not set host_buffer consider it programmer error + if self.context.entry_point_type() == EntryPointType::Session + && runtime.context.entry_point_type() == EntryPointType::Session + { + // Overwrites parent's named keys with child's new named key but only when + // running session code + *self.context.named_keys_mut() = runtime.context.named_keys().clone(); + } + return runtime.take_host_buffer().ok_or(Error::ExpectedReturnValue); + } + error => return Err(error.clone()), + } + } + + Err(Error::Interpreter(error.into())) + } + + fn call_contract_host_buffer( + &mut self, + contract_hash: ContractHash, + entry_point_name: &str, + args_bytes: Vec, + result_size_ptr: u32, + scoped_instrumenter: &mut ScopedInstrumenter, + ) -> Result, Error> { + // Exit early if the host buffer is already occupied + if let Err(err) = self.check_host_buffer() { + return Ok(Err(err)); + } + let args: RuntimeArgs = bytesrepr::deserialize(args_bytes)?; + scoped_instrumenter.pause(); + let result = self.call_contract(contract_hash, entry_point_name, args)?; + scoped_instrumenter.unpause(); + self.manage_call_contract_host_buffer(result_size_ptr, result) + } + + fn call_versioned_contract_host_buffer( + &mut self, + contract_package_hash: ContractPackageHash, + contract_version: Option, + entry_point_name: String, + args_bytes: Vec, + result_size_ptr: u32, + scoped_instrumenter: &mut ScopedInstrumenter, + ) -> Result, Error> { + // Exit early if the host buffer is already occupied + if let Err(err) = self.check_host_buffer() { + return Ok(Err(err)); + } + let args: RuntimeArgs = bytesrepr::deserialize(args_bytes)?; + scoped_instrumenter.pause(); + let result = self.call_versioned_contract( + contract_package_hash, + contract_version, + entry_point_name, + args, + )?; + scoped_instrumenter.unpause(); + self.manage_call_contract_host_buffer(result_size_ptr, result) + } + + fn check_host_buffer(&mut self) -> Result<(), ApiError> { + if !self.can_write_to_host_buffer() { + Err(ApiError::HostBufferFull) + } else { + Ok(()) + } + } + + fn manage_call_contract_host_buffer( + &mut self, + result_size_ptr: u32, + result: CLValue, + ) -> Result, Error> { + let result_size = result.inner_bytes().len() as u32; // considered to be safe + + // leave the host buffer set to `None` if there's nothing to write there + if result_size != 0 { + if let Err(error) = self.write_host_buffer(result) { + return Ok(Err(error)); + } + } + + let result_size_bytes = result_size.to_le_bytes(); // Wasm is little-endian + if let Err(error) = self.memory.set(result_size_ptr, &result_size_bytes) { + return Err(Error::Interpreter(error.into())); + } + + Ok(Ok(())) + } + + fn load_named_keys( + &mut self, + total_keys_ptr: u32, + result_size_ptr: u32, + scoped_instrumenter: &mut ScopedInstrumenter, + ) -> Result, Trap> { + scoped_instrumenter.add_property( + "names_total_length", + self.context + .named_keys() + .keys() + .map(|name| name.len()) + .sum::(), + ); + + if !self.can_write_to_host_buffer() { + // Exit early if the host buffer is already occupied + return Ok(Err(ApiError::HostBufferFull)); + } + + let total_keys = self.context.named_keys().len() as u32; + let total_keys_bytes = total_keys.to_le_bytes(); + if let Err(error) = self.memory.set(total_keys_ptr, &total_keys_bytes) { + return Err(Error::Interpreter(error.into()).into()); + } + + if total_keys == 0 { + // No need to do anything else, we leave host buffer empty. + return Ok(Ok(())); + } + + let named_keys = + CLValue::from_t(self.context.named_keys().clone()).map_err(Error::CLValue)?; + + let length = named_keys.inner_bytes().len() as u32; + if let Err(error) = self.write_host_buffer(named_keys) { + return Ok(Err(error)); + } + + let length_bytes = length.to_le_bytes(); + if let Err(error) = self.memory.set(result_size_ptr, &length_bytes) { + return Err(Error::Interpreter(error.into()).into()); + } + + Ok(Ok(())) + } + + fn create_contract_value(&mut self) -> Result<(StoredValue, URef), Error> { + let access_key = self.context.new_unit_uref()?; + let contract_package = ContractPackage::new( + access_key, + ContractVersions::default(), + DisabledVersions::default(), + Groups::default(), + ); + + let value = StoredValue::ContractPackage(contract_package); + + Ok((value, access_key)) + } + + fn create_contract_package_at_hash(&mut self) -> Result<([u8; 32], [u8; 32]), Error> { + let addr = self.context.new_hash_address()?; + let key = Key::Hash(addr); + let (stored_value, access_key) = self.create_contract_value()?; + + self.context.state().borrow_mut().write(key, stored_value); + Ok((addr, access_key.addr())) + } + + fn create_contract_user_group( + &mut self, + contract_package_hash: ContractPackageHash, + label: String, + num_new_urefs: u32, + mut existing_urefs: BTreeSet, + output_size_ptr: u32, + ) -> Result, Error> { + let contract_package_key = contract_package_hash.into(); + + let mut contract_package: ContractPackage = self + .context + .get_validated_contract_package(contract_package_hash)?; + + let groups = contract_package.groups_mut(); + let new_group = Group::new(label); + + // Ensure group does not already exist + if groups.get(&new_group).is_some() { + return Ok(Err(contracts::Error::GroupAlreadyExists.into())); + } + + // Ensure there are not too many groups + if groups.len() >= (contracts::MAX_GROUPS as usize) { + return Ok(Err(contracts::Error::MaxGroupsExceeded.into())); + } + + // Ensure there are not too many urefs + let total_urefs: usize = groups.values().map(|urefs| urefs.len()).sum::() + + (num_new_urefs as usize) + + existing_urefs.len(); + if total_urefs > contracts::MAX_TOTAL_UREFS { + let err = contracts::Error::MaxTotalURefsExceeded; + return Ok(Err(ApiError::ContractHeader(err as u8))); + } + + // Proceed with creating user group + let mut new_urefs = Vec::with_capacity(num_new_urefs as usize); + for _ in 0..num_new_urefs { + let u = self.context.new_unit_uref()?; + new_urefs.push(u); + } + + for u in new_urefs.iter().cloned() { + existing_urefs.insert(u); + } + groups.insert(new_group, existing_urefs); + + // check we can write to the host buffer + if let Err(err) = self.check_host_buffer() { + return Ok(Err(err)); + } + // create CLValue for return value + let new_urefs_value = CLValue::from_t(new_urefs)?; + let value_size = new_urefs_value.inner_bytes().len(); + // write return value to buffer + if let Err(err) = self.write_host_buffer(new_urefs_value) { + return Ok(Err(err)); + } + // Write return value size to output location + let output_size_bytes = value_size.to_le_bytes(); // Wasm is little-endian + if let Err(error) = self.memory.set(output_size_ptr, &output_size_bytes) { + return Err(Error::Interpreter(error.into())); + } + + // Write updated package to the global state + self.context.state().borrow_mut().write( + contract_package_key, + StoredValue::ContractPackage(contract_package), + ); + + Ok(Ok(())) + } + + #[allow(clippy::too_many_arguments)] + fn add_contract_version( + &mut self, + contract_package_hash: ContractPackageHash, + entry_points: EntryPoints, + mut named_keys: NamedKeys, + output_ptr: u32, + output_size: usize, + bytes_written_ptr: u32, + version_ptr: u32, + ) -> Result, Error> { + let contract_package_key = contract_package_hash.into(); + self.context.validate_key(&contract_package_key)?; + + let mut contract_package: ContractPackage = self + .context + .get_validated_contract_package(contract_package_hash)?; + + let contract_wasm_hash = self.context.new_hash_address()?; + let contract_wasm_key = Key::Hash(contract_wasm_hash); + let contract_wasm = { + let module_bytes = self.get_module_from_entry_points(&entry_points)?; + ContractWasm::new(module_bytes) + }; + + let contract_hash = self.context.new_hash_address()?; + let contract_key = Key::Hash(contract_hash); + + let protocol_version = self.context.protocol_version(); + let major = protocol_version.value().major; + + // TODO: EE-1032 - Implement different ways of carrying on existing named keys + if let Some(previous_contract_hash) = contract_package.current_contract_hash() { + let previous_contract: Contract = + self.context.read_gs_typed(&previous_contract_hash.into())?; + + let mut previous_named_keys = previous_contract.take_named_keys(); + named_keys.append(&mut previous_named_keys); + } + + let contract = Contract::new( + contract_package_hash, + contract_wasm_hash, + named_keys, + entry_points, + protocol_version, + ); + + let insert_contract_result = contract_package.insert_contract_version(major, contract_hash); + + self.context + .state() + .borrow_mut() + .write(contract_wasm_key, StoredValue::ContractWasm(contract_wasm)); + + self.context + .state() + .borrow_mut() + .write(contract_key, StoredValue::Contract(contract)); + + self.context.state().borrow_mut().write( + contract_package_key, + StoredValue::ContractPackage(contract_package), + ); + + // return contract key to caller + { + let key_bytes = match contract_hash.to_bytes() { + Ok(bytes) => bytes, + Err(error) => return Ok(Err(error.into())), + }; + + // `output_size` must be >= actual length of serialized Key bytes + if output_size < key_bytes.len() { + return Ok(Err(ApiError::BufferTooSmall)); + } + + // Set serialized Key bytes into the output buffer + if let Err(error) = self.memory.set(output_ptr, &key_bytes) { + return Err(Error::Interpreter(error.into())); + } + + // Following cast is assumed to be safe + let bytes_size = key_bytes.len() as u32; + let size_bytes = bytes_size.to_le_bytes(); // Wasm is little-endian + if let Err(error) = self.memory.set(bytes_written_ptr, &size_bytes) { + return Err(Error::Interpreter(error.into())); + } + + let version_value: u32 = insert_contract_result.contract_version(); + let version_bytes = version_value.to_le_bytes(); + if let Err(error) = self.memory.set(version_ptr, &version_bytes) { + return Err(Error::Interpreter(error.into())); + } + } + + Ok(Ok(())) + } + + fn disable_contract_version( + &mut self, + contract_package_hash: ContractPackageHash, + contract_hash: ContractHash, + ) -> Result, Error> { + let contract_package_key = contract_package_hash.into(); + self.context.validate_key(&contract_package_key)?; + + let mut contract_package: ContractPackage = self + .context + .get_validated_contract_package(contract_package_hash)?; + + if let Err(err) = contract_package.disable_contract_version(contract_hash) { + return Ok(Err(err.into())); + } + + self.context.state().borrow_mut().write( + contract_package_key, + StoredValue::ContractPackage(contract_package), + ); + + Ok(Ok(())) + } + + /// Writes function address (`hash_bytes`) into the Wasm memory (at + /// `dest_ptr` pointer). + fn function_address(&mut self, hash_bytes: [u8; 32], dest_ptr: u32) -> Result<(), Trap> { + self.memory + .set(dest_ptr, &hash_bytes) + .map_err(|e| Error::Interpreter(e.into()).into()) + } + + /// Generates new unforgable reference and adds it to the context's + /// access_rights set. + fn new_uref(&mut self, uref_ptr: u32, value_ptr: u32, value_size: u32) -> Result<(), Trap> { + let cl_value = self.cl_value_from_mem(value_ptr, value_size)?; // read initial value from memory + let uref = self.context.new_uref(StoredValue::CLValue(cl_value))?; + self.memory + .set(uref_ptr, &uref.into_bytes().map_err(Error::BytesRepr)?) + .map_err(|e| Error::Interpreter(e.into()).into()) + } + + /// Writes `value` under `key` in GlobalState. + fn write( + &mut self, + key_ptr: u32, + key_size: u32, + value_ptr: u32, + value_size: u32, + ) -> Result<(), Trap> { + let key = self.key_from_mem(key_ptr, key_size)?; + let cl_value = self.cl_value_from_mem(value_ptr, value_size)?; + self.context + .write_gs(key, StoredValue::CLValue(cl_value)) + .map_err(Into::into) + } + + /// Writes `value` under a key derived from `key` in the "local cluster" of + /// GlobalState + fn write_local( + &mut self, + key_ptr: u32, + key_size: u32, + value_ptr: u32, + value_size: u32, + ) -> Result<(), Trap> { + let key_bytes = self.bytes_from_mem(key_ptr, key_size as usize)?; + let cl_value = self.cl_value_from_mem(value_ptr, value_size)?; + self.context + .write_ls(&key_bytes, cl_value) + .map_err(Into::into) + } + + /// Adds `value` to the cell that `key` points at. + fn add( + &mut self, + key_ptr: u32, + key_size: u32, + value_ptr: u32, + value_size: u32, + ) -> Result<(), Trap> { + let key = self.key_from_mem(key_ptr, key_size)?; + let cl_value = self.cl_value_from_mem(value_ptr, value_size)?; + self.context + .add_gs(key, StoredValue::CLValue(cl_value)) + .map_err(Into::into) + } + + /// Reads value from the GS living under key specified by `key_ptr` and + /// `key_size`. Wasm and host communicate through memory that Wasm + /// module exports. If contract wants to pass data to the host, it has + /// to tell it [the host] where this data lives in the exported memory + /// (pass its pointer and length). + fn read( + &mut self, + key_ptr: u32, + key_size: u32, + output_size_ptr: u32, + ) -> Result, Trap> { + if !self.can_write_to_host_buffer() { + // Exit early if the host buffer is already occupied + return Ok(Err(ApiError::HostBufferFull)); + } + + let key = self.key_from_mem(key_ptr, key_size)?; + let cl_value = match self.context.read_gs(&key)? { + Some(stored_value) => CLValue::try_from(stored_value).map_err(Error::TypeMismatch)?, + None => return Ok(Err(ApiError::ValueNotFound)), + }; + + let value_size = cl_value.inner_bytes().len() as u32; + if let Err(error) = self.write_host_buffer(cl_value) { + return Ok(Err(error)); + } + + let value_bytes = value_size.to_le_bytes(); // Wasm is little-endian + if let Err(error) = self.memory.set(output_size_ptr, &value_bytes) { + return Err(Error::Interpreter(error.into()).into()); + } + + Ok(Ok(())) + } + + /// Similar to `read`, this function is for reading from the "local cluster" + /// of global state + fn read_local( + &mut self, + key_ptr: u32, + key_size: u32, + output_size_ptr: u32, + ) -> Result, Trap> { + if !self.can_write_to_host_buffer() { + // Exit early if the host buffer is already occupied + return Ok(Err(ApiError::HostBufferFull)); + } + + let key_bytes = self.bytes_from_mem(key_ptr, key_size as usize)?; + + let cl_value = match self.context.read_ls(&key_bytes)? { + Some(cl_value) => cl_value, + None => return Ok(Err(ApiError::ValueNotFound)), + }; + + let value_size = cl_value.inner_bytes().len() as u32; + if let Err(error) = self.write_host_buffer(cl_value) { + return Ok(Err(error)); + } + + let value_bytes = value_size.to_le_bytes(); // Wasm is little-endian + if let Err(error) = self.memory.set(output_size_ptr, &value_bytes) { + return Err(Error::Interpreter(error.into()).into()); + } + + Ok(Ok(())) + } + + /// Reverts contract execution with a status specified. + fn revert(&mut self, status: u32) -> Trap { + Error::Revert(status.into()).into() + } + + fn add_associated_key( + &mut self, + account_hash_ptr: u32, + account_hash_size: usize, + weight_value: u8, + ) -> Result { + let account_hash = { + // Account hash as serialized bytes + let source_serialized = self.bytes_from_mem(account_hash_ptr, account_hash_size)?; + // Account hash deserialized + let source: AccountHash = + bytesrepr::deserialize(source_serialized).map_err(Error::BytesRepr)?; + source + }; + let weight = Weight::new(weight_value); + + match self.context.add_associated_key(account_hash, weight) { + Ok(_) => Ok(0), + // This relies on the fact that `AddKeyFailure` is represented as + // i32 and first variant start with number `1`, so all other variants + // are greater than the first one, so it's safe to assume `0` is success, + // and any error is greater than 0. + Err(Error::AddKeyFailure(e)) => Ok(e as i32), + // Any other variant just pass as `Trap` + Err(e) => Err(e.into()), + } + } + + fn remove_associated_key( + &mut self, + account_hash_ptr: u32, + account_hash_size: usize, + ) -> Result { + let account_hash = { + // Account hash as serialized bytes + let source_serialized = self.bytes_from_mem(account_hash_ptr, account_hash_size)?; + // Account hash deserialized + let source: AccountHash = + bytesrepr::deserialize(source_serialized).map_err(Error::BytesRepr)?; + source + }; + match self.context.remove_associated_key(account_hash) { + Ok(_) => Ok(0), + Err(Error::RemoveKeyFailure(e)) => Ok(e as i32), + Err(e) => Err(e.into()), + } + } + + fn update_associated_key( + &mut self, + account_hash_ptr: u32, + account_hash_size: usize, + weight_value: u8, + ) -> Result { + let account_hash = { + // Account hash as serialized bytes + let source_serialized = self.bytes_from_mem(account_hash_ptr, account_hash_size)?; + // Account hash deserialized + let source: AccountHash = + bytesrepr::deserialize(source_serialized).map_err(Error::BytesRepr)?; + source + }; + let weight = Weight::new(weight_value); + + match self.context.update_associated_key(account_hash, weight) { + Ok(_) => Ok(0), + // This relies on the fact that `UpdateKeyFailure` is represented as + // i32 and first variant start with number `1`, so all other variants + // are greater than the first one, so it's safe to assume `0` is success, + // and any error is greater than 0. + Err(Error::UpdateKeyFailure(e)) => Ok(e as i32), + // Any other variant just pass as `Trap` + Err(e) => Err(e.into()), + } + } + + fn set_action_threshold( + &mut self, + action_type_value: u32, + threshold_value: u8, + ) -> Result { + match ActionType::try_from(action_type_value) { + Ok(action_type) => { + let threshold = Weight::new(threshold_value); + match self.context.set_action_threshold(action_type, threshold) { + Ok(_) => Ok(0), + Err(Error::SetThresholdFailure(e)) => Ok(e as i32), + Err(e) => Err(e.into()), + } + } + Err(_) => Err(Trap::new(TrapKind::Unreachable)), + } + } + + /// Looks up the public mint contract key in the context's protocol data. + /// + /// Returned URef is already attenuated depending on the calling account. + fn get_mint_contract(&self) -> ContractHash { + self.context.protocol_data().mint() + } + + /// Looks up the public PoS contract key in the context's protocol data. + /// + /// Returned URef is already attenuated depending on the calling account. + fn get_pos_contract(&self) -> ContractHash { + self.context.protocol_data().proof_of_stake() + } + + /// Looks up the public standard payment contract key in the context's protocol data. + /// + /// Returned URef is already attenuated depending on the calling account. + fn get_standard_payment_contract(&self) -> ContractHash { + self.context.protocol_data().standard_payment() + } + + /// Calls the "create" method on the mint contract at the given mint + /// contract key + fn mint_create(&mut self, mint_contract_hash: ContractHash) -> Result { + let result = self.call_contract(mint_contract_hash, "create", RuntimeArgs::new())?; + let purse = result.into_t()?; + + Ok(purse) + } + + fn create_purse(&mut self) -> Result { + self.mint_create(self.get_mint_contract()) + } + + /// Calls the "transfer" method on the mint contract at the given mint + /// contract key + fn mint_transfer( + &mut self, + mint_contract_hash: ContractHash, + source: URef, + target: URef, + amount: U512, + ) -> Result<(), Error> { + const ARG_SOURCE: &str = "source"; + const ARG_TARGET: &str = "target"; + const ARG_AMOUNT: &str = "amount"; + + let args_values: RuntimeArgs = runtime_args! { + ARG_SOURCE => source, + ARG_TARGET => target, + ARG_AMOUNT => amount, + }; + + let result = self.call_contract(mint_contract_hash, "transfer", args_values)?; + let result: Result<(), mint::Error> = result.into_t()?; + Ok(result.map_err(system_contract_errors::Error::from)?) + } + + /// Creates a new account at a given public key, transferring a given amount + /// of motes from the given source purse to the new account's purse. + fn transfer_to_new_account( + &mut self, + source: URef, + target: AccountHash, + amount: U512, + ) -> Result { + let mint_contract_hash = self.get_mint_contract(); + + let target_key = Key::Account(target); + + // A precondition check that verifies that the transfer can be done + // as the source purse has enough funds to cover the transfer. + if amount > self.get_balance(source)?.unwrap_or_default() { + return Ok(Err(ApiError::Transfer)); + } + + let target_purse = self.mint_create(mint_contract_hash)?; + + if source == target_purse { + return Ok(Err(ApiError::Transfer)); + } + + match self.mint_transfer(mint_contract_hash, source, target_purse, amount) { + Ok(_) => { + let account = Account::create(target, Default::default(), target_purse); + self.context.write_account(target_key, account)?; + Ok(Ok(TransferredTo::NewAccount)) + } + Err(_) => Ok(Err(ApiError::Transfer)), + } + } + + /// Transferring a given amount of motes from the given source purse to the + /// new account's purse. Requires that the [`URef`]s have already + /// been created by the mint contract (or are the genesis account's). + fn transfer_to_existing_account( + &mut self, + source: URef, + target: URef, + amount: U512, + ) -> Result { + let mint_contract_key = self.get_mint_contract(); + + // This appears to be a load-bearing use of `RuntimeContext::insert_uref`. + self.context.insert_uref(target); + + match self.mint_transfer(mint_contract_key, source, target, amount) { + Ok(_) => Ok(Ok(TransferredTo::ExistingAccount)), + Err(_) => Ok(Err(ApiError::Transfer)), + } + } + + /// Transfers `amount` of motes from default purse of the account to + /// `target` account. If that account does not exist, creates one. + fn transfer_to_account( + &mut self, + target: AccountHash, + amount: U512, + ) -> Result { + let source = self.context.get_main_purse()?; + self.transfer_from_purse_to_account(source, target, amount) + } + + /// Transfers `amount` of motes from `source` purse to `target` account. + /// If that account does not exist, creates one. + fn transfer_from_purse_to_account( + &mut self, + source: URef, + target: AccountHash, + amount: U512, + ) -> Result { + let target_key = Key::Account(target); + // Look up the account at the given public key's address + match self.context.read_account(&target_key)? { + None => { + // If no account exists, create a new account and transfer the amount to its + // purse. + self.transfer_to_new_account(source, target, amount) + } + Some(StoredValue::Account(account)) => { + let target = account.main_purse_add_only(); + if source == target { + return Ok(Ok(TransferredTo::ExistingAccount)); + } + // If an account exists, transfer the amount to its purse + self.transfer_to_existing_account(source, target, amount) + } + Some(_) => { + // If some other value exists, return an error + Err(Error::AccountNotFound(target_key)) + } + } + } + + /// Transfers `amount` of motes from `source` purse to `target` purse. + fn transfer_from_purse_to_purse( + &mut self, + source_ptr: u32, + source_size: u32, + target_ptr: u32, + target_size: u32, + amount_ptr: u32, + amount_size: u32, + ) -> Result, Error> { + let source: URef = { + let bytes = self.bytes_from_mem(source_ptr, source_size as usize)?; + bytesrepr::deserialize(bytes).map_err(Error::BytesRepr)? + }; + + let target: URef = { + let bytes = self.bytes_from_mem(target_ptr, target_size as usize)?; + bytesrepr::deserialize(bytes).map_err(Error::BytesRepr)? + }; + + let amount: U512 = { + let bytes = self.bytes_from_mem(amount_ptr, amount_size as usize)?; + bytesrepr::deserialize(bytes).map_err(Error::BytesRepr)? + }; + + let mint_contract_key = self.get_mint_contract(); + + if self + .mint_transfer(mint_contract_key, source, target, amount) + .is_ok() + { + Ok(Ok(())) + } else { + Ok(Err(ApiError::Transfer)) + } + } + + fn get_balance(&mut self, purse: URef) -> Result, Error> { + let key = purse.addr(); + + let uref_key = match self.context.read_ls(&key)? { + Some(cl_value) => { + let key: Key = cl_value.into_t().expect("expected Key type"); + match key { + Key::URef(_) => (), + _ => panic!("expected Key::Uref(_)"), + } + key + } + None => return Ok(None), + }; + + let ret = match self.context.read_gs_direct(&uref_key)? { + Some(StoredValue::CLValue(cl_value)) => { + if *cl_value.cl_type() == CLType::U512 { + let balance: U512 = cl_value.into_t()?; + Some(balance) + } else { + panic!("expected U512") + } + } + Some(_) => panic!("expected U512"), + None => None, + }; + + Ok(ret) + } + + fn get_balance_host_buffer( + &mut self, + purse_ptr: u32, + purse_size: usize, + output_size_ptr: u32, + ) -> Result, Error> { + if !self.can_write_to_host_buffer() { + // Exit early if the host buffer is already occupied + return Ok(Err(ApiError::HostBufferFull)); + } + + let purse: URef = { + let bytes = self.bytes_from_mem(purse_ptr, purse_size)?; + match bytesrepr::deserialize(bytes) { + Ok(purse) => purse, + Err(error) => return Ok(Err(error.into())), + } + }; + + let balance = match self.get_balance(purse)? { + Some(balance) => balance, + None => return Ok(Err(ApiError::InvalidPurse)), + }; + + let balance_cl_value = match CLValue::from_t(balance) { + Ok(cl_value) => cl_value, + Err(error) => return Ok(Err(error.into())), + }; + + let balance_size = balance_cl_value.inner_bytes().len() as i32; + if let Err(error) = self.write_host_buffer(balance_cl_value) { + return Ok(Err(error)); + } + + let balance_size_bytes = balance_size.to_le_bytes(); // Wasm is little-endian + if let Err(error) = self.memory.set(output_size_ptr, &balance_size_bytes) { + return Err(Error::Interpreter(error.into())); + } + + Ok(Ok(())) + } + + fn get_system_contract( + &mut self, + system_contract_index: u32, + dest_ptr: u32, + _dest_size: u32, + ) -> Result, Trap> { + let contract_hash: ContractHash = match SystemContractType::try_from(system_contract_index) + { + Ok(SystemContractType::Mint) => self.get_mint_contract(), + Ok(SystemContractType::ProofOfStake) => self.get_pos_contract(), + Ok(SystemContractType::StandardPayment) => self.get_standard_payment_contract(), + Err(error) => return Ok(Err(error)), + }; + + match self.memory.set(dest_ptr, &contract_hash) { + Ok(_) => Ok(Ok(())), + Err(error) => Err(Error::Interpreter(error.into()).into()), + } + } + + /// If host_buffer set, clears the host_buffer and returns value, else None + pub fn take_host_buffer(&mut self) -> Option { + self.host_buffer.take() + } + + /// Checks if a write to host buffer can happen. + /// + /// This will check if the host buffer is empty. + fn can_write_to_host_buffer(&self) -> bool { + self.host_buffer.is_none() + } + + /// Overwrites data in host buffer only if it's in empty state + fn write_host_buffer(&mut self, data: CLValue) -> Result<(), ApiError> { + match self.host_buffer { + Some(_) => return Err(ApiError::HostBufferFull), + None => self.host_buffer = Some(data), + } + Ok(()) + } + + fn read_host_buffer( + &mut self, + dest_ptr: u32, + dest_size: usize, + bytes_written_ptr: u32, + ) -> Result, Error> { + let (_cl_type, serialized_value) = match self.take_host_buffer() { + None => return Ok(Err(ApiError::HostBufferEmpty)), + Some(cl_value) => cl_value.destructure(), + }; + + if serialized_value.len() > u32::max_value() as usize { + return Ok(Err(ApiError::OutOfMemory)); + } + if serialized_value.len() > dest_size { + return Ok(Err(ApiError::BufferTooSmall)); + } + + // Slice data, so if `dest_size` is larger than host_buffer size, it will take host_buffer + // as whole. + let sliced_buf = &serialized_value[..cmp::min(dest_size, serialized_value.len())]; + if let Err(error) = self.memory.set(dest_ptr, sliced_buf) { + return Err(Error::Interpreter(error.into())); + } + + let bytes_written = sliced_buf.len() as u32; + let bytes_written_data = bytes_written.to_le_bytes(); + + if let Err(error) = self.memory.set(bytes_written_ptr, &bytes_written_data) { + return Err(Error::Interpreter(error.into())); + } + + Ok(Ok(())) + } + + #[cfg(feature = "test-support")] + fn print(&mut self, text_ptr: u32, text_size: u32) -> Result<(), Trap> { + let text = self.string_from_mem(text_ptr, text_size)?; + println!("{}", text); + Ok(()) + } + + fn get_named_arg_size( + &mut self, + name_ptr: u32, + name_size: usize, + size_ptr: u32, + ) -> Result, Trap> { + let name_bytes = self.bytes_from_mem(name_ptr, name_size)?; + let name = String::from_utf8_lossy(&name_bytes); + + let arg_size = match self.context.args().get(&name) { + Some(arg) if arg.inner_bytes().len() > u32::max_value() as usize => { + return Ok(Err(ApiError::OutOfMemory)); + } + Some(arg) => arg.inner_bytes().len() as u32, + None => return Ok(Err(ApiError::MissingArgument)), + }; + + let arg_size_bytes = arg_size.to_le_bytes(); // Wasm is little-endian + + if let Err(e) = self.memory.set(size_ptr, &arg_size_bytes) { + return Err(Error::Interpreter(e.into()).into()); + } + + Ok(Ok(())) + } + + fn get_named_arg( + &mut self, + name_ptr: u32, + name_size: usize, + output_ptr: u32, + output_size: usize, + ) -> Result, Trap> { + let name_bytes = self.bytes_from_mem(name_ptr, name_size)?; + let name = String::from_utf8_lossy(&name_bytes); + + let arg = match self.context.args().get(&name) { + Some(arg) => arg, + None => return Ok(Err(ApiError::MissingArgument)), + }; + + if arg.inner_bytes().len() > output_size { + return Ok(Err(ApiError::OutOfMemory)); + } + + if let Err(e) = self + .memory + .set(output_ptr, &arg.inner_bytes()[..output_size]) + { + return Err(Error::Interpreter(e.into()).into()); + } + + Ok(Ok(())) + } + + fn validate_entry_point_access( + &self, + package: &ContractPackage, + access: &EntryPointAccess, + ) -> Result<(), Error> { + runtime_context::validate_entry_point_access_with(package, access, |uref| { + self.context.validate_uref(uref).is_ok() + }) + } + + /// Remove a user group from access to a contract + fn remove_contract_user_group( + &mut self, + package_key: ContractPackageHash, + label: Group, + ) -> Result, Error> { + let mut package: ContractPackage = + self.context.get_validated_contract_package(package_key)?; + + let group_to_remove = Group::new(label); + let groups = package.groups_mut(); + + // Ensure group exists in groups + if groups.get(&group_to_remove).is_none() { + return Ok(Err(contracts::Error::GroupDoesNotExist.into())); + } + + // Remove group if it is not referenced by at least one entry_point in active versions. + let versions = package.versions(); + for contract_hash in versions.values() { + let entry_points = { + let contract: Contract = self.context.read_gs_typed(&Key::from(*contract_hash))?; + contract.entry_points().clone().take_entry_points() + }; + for entry_point in entry_points { + match entry_point.access() { + EntryPointAccess::Public => { + continue; + } + EntryPointAccess::Groups(groups) => { + if groups.contains(&group_to_remove) { + return Ok(Err(contracts::Error::GroupInUse.into())); + } + } + } + } + } + + if !package.remove_group(&group_to_remove) { + return Ok(Err(contracts::Error::GroupInUse.into())); + } + + // Write updated package to the global state + self.context.state().borrow_mut().write( + Key::from(package_key), + StoredValue::ContractPackage(package), + ); + Ok(Ok(())) + } + + #[allow(clippy::too_many_arguments)] + fn provision_contract_user_group_uref( + &mut self, + package_ptr: u32, + package_size: u32, + label_ptr: u32, + label_size: u32, + output_size_ptr: u32, + ) -> Result, Error> { + let contract_package_hash = self.t_from_mem(package_ptr, package_size)?; + let label: String = self.t_from_mem(label_ptr, label_size)?; + let mut contract_package = self + .context + .get_validated_contract_package(contract_package_hash)?; + let groups = contract_package.groups_mut(); + + let group_label = Group::new(label); + + // Ensure there are not too many urefs + let total_urefs: usize = groups.values().map(|urefs| urefs.len()).sum(); + + if total_urefs + 1 > contracts::MAX_TOTAL_UREFS { + return Ok(Err(contracts::Error::MaxTotalURefsExceeded.into())); + } + + // Ensure given group exists and does not exceed limits + let group = match groups.get_mut(&group_label) { + Some(group) if group.len() + 1 > contracts::MAX_GROUPS as usize => { + // Ensures there are not too many groups to fit in amount of new urefs + return Ok(Err(contracts::Error::MaxTotalURefsExceeded.into())); + } + Some(group) => group, + None => return Ok(Err(contracts::Error::GroupDoesNotExist.into())), + }; + + // Proceed with creating new URefs + let new_uref = self.context.new_unit_uref()?; + if !group.insert(new_uref) { + return Ok(Err(contracts::Error::URefAlreadyExists.into())); + } + + // check we can write to the host buffer + if let Err(err) = self.check_host_buffer() { + return Ok(Err(err)); + } + // create CLValue for return value + let new_uref_value = CLValue::from_t(new_uref)?; + let value_size = new_uref_value.inner_bytes().len(); + // write return value to buffer + if let Err(err) = self.write_host_buffer(new_uref_value) { + return Ok(Err(err)); + } + // Write return value size to output location + let output_size_bytes = value_size.to_le_bytes(); // Wasm is little-endian + if let Err(error) = self.memory.set(output_size_ptr, &output_size_bytes) { + return Err(Error::Interpreter(error.into())); + } + + // Write updated package to the global state + self.context.state().borrow_mut().write( + Key::from(contract_package_hash), + StoredValue::ContractPackage(contract_package), + ); + + Ok(Ok(())) + } + + #[allow(clippy::too_many_arguments)] + fn remove_contract_user_group_urefs( + &mut self, + package_ptr: u32, + package_size: u32, + label_ptr: u32, + label_size: u32, + urefs_ptr: u32, + urefs_size: u32, + ) -> Result, Error> { + let contract_package_hash: ContractPackageHash = + self.t_from_mem(package_ptr, package_size)?; + let label: String = self.t_from_mem(label_ptr, label_size)?; + let urefs: BTreeSet = self.t_from_mem(urefs_ptr, urefs_size)?; + + let mut contract_package = self + .context + .get_validated_contract_package(contract_package_hash)?; + + let groups = contract_package.groups_mut(); + let group_label = Group::new(label); + + let group = match groups.get_mut(&group_label) { + Some(group) => group, + None => return Ok(Err(contracts::Error::GroupDoesNotExist.into())), + }; + + if urefs.is_empty() { + return Ok(Ok(())); + } + + for uref in urefs { + if !group.remove(&uref) { + return Ok(Err(contracts::Error::UnableToRemoveURef.into())); + } + } + // Write updated package to the global state + self.context.state().borrow_mut().write( + Key::from(contract_package_hash), + StoredValue::ContractPackage(contract_package), + ); + + Ok(Ok(())) + } +} + +#[cfg(test)] +mod tests { + use proptest::{ + array, + collection::{btree_map, vec}, + option, + prelude::*, + result, + }; + + use types::{gens::*, CLType, CLValue, Key, URef}; + + use super::extract_urefs; + + fn cl_value_with_urefs_arb() -> impl Strategy)> { + // If compiler brings you here it most probably means you've added a variant to `CLType` + // enum but forgot to add generator for it. + let stub: Option = None; + if let Some(cl_type) = stub { + match cl_type { + CLType::Bool + | CLType::I32 + | CLType::I64 + | CLType::U8 + | CLType::U32 + | CLType::U64 + | CLType::U128 + | CLType::U256 + | CLType::U512 + | CLType::Unit + | CLType::String + | CLType::Key + | CLType::URef + | CLType::Option(_) + | CLType::List(_) + | CLType::FixedList(..) + | CLType::Result { .. } + | CLType::Map { .. } + | CLType::Tuple1(_) + | CLType::Tuple2(_) + | CLType::Tuple3(_) + | CLType::Any => (), + } + }; + + prop_oneof![ + Just((CLValue::from_t(()).expect("should create CLValue"), vec![])), + any::() + .prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + any::().prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + any::().prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + any::().prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + any::().prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + any::().prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + u128_arb().prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + u256_arb().prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + u512_arb().prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + key_arb().prop_map(|x| { + let urefs = x.as_uref().into_iter().cloned().collect(); + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + uref_arb().prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![x])), + ".*".prop_map(|x: String| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + option::of(any::()) + .prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + option::of(uref_arb()).prop_map(|x| { + let urefs = x.iter().cloned().collect(); + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + option::of(key_arb()).prop_map(|x| { + let urefs = x.iter().filter_map(Key::as_uref).cloned().collect(); + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + vec(any::(), 0..100) + .prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + vec(uref_arb(), 0..100).prop_map(|x| ( + CLValue::from_t(x.clone()).expect("should create CLValue"), + x + )), + vec(key_arb(), 0..100).prop_map(|x| ( + CLValue::from_t(x.clone()).expect("should create CLValue"), + x.into_iter().filter_map(Key::into_uref).collect() + )), + [any::(); 32] + .prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + array::uniform8(uref_arb()).prop_map(|x| { + let urefs = x.to_vec(); + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + array::uniform8(key_arb()).prop_map(|x| { + let urefs = x.iter().filter_map(Key::as_uref).cloned().collect(); + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + result::maybe_err(key_arb(), ".*").prop_map(|x| { + let urefs = match &x { + Ok(key) => key.as_uref().into_iter().cloned().collect(), + Err(_) => vec![], + }; + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + result::maybe_ok(".*", uref_arb()).prop_map(|x| { + let urefs = match &x { + Ok(_) => vec![], + Err(uref) => vec![*uref], + }; + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + btree_map(".*", u512_arb(), 0..100) + .prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + btree_map(uref_arb(), u512_arb(), 0..100).prop_map(|x| { + let urefs = x.keys().cloned().collect(); + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + btree_map(".*", uref_arb(), 0..100).prop_map(|x| { + let urefs = x.values().cloned().collect(); + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + btree_map(uref_arb(), key_arb(), 0..100).prop_map(|x| { + let mut urefs: Vec = x.keys().cloned().collect(); + urefs.extend(x.values().filter_map(Key::as_uref).cloned()); + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + (any::()) + .prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + (uref_arb()) + .prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![x])), + (any::(), any::()) + .prop_map(|x| (CLValue::from_t(x).expect("should create CLValue"), vec![])), + (uref_arb(), any::()).prop_map(|x| { + let uref = x.0; + ( + CLValue::from_t(x).expect("should create CLValue"), + vec![uref], + ) + }), + (any::(), key_arb()).prop_map(|x| { + let urefs = x.1.as_uref().into_iter().cloned().collect(); + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + (uref_arb(), key_arb()).prop_map(|x| { + let mut urefs = vec![x.0]; + urefs.extend(x.1.as_uref().into_iter().cloned()); + (CLValue::from_t(x).expect("should create CLValue"), urefs) + }), + ] + } + + proptest! { + #[test] + fn should_extract_urefs((cl_value, urefs) in cl_value_with_urefs_arb()) { + let extracted_urefs = extract_urefs(&cl_value).unwrap(); + assert_eq!(extracted_urefs, urefs); + } + } +} diff --git a/node/src/contract_core/runtime/proof_of_stake_internal.rs b/node/src/contract_core/runtime/proof_of_stake_internal.rs new file mode 100644 index 0000000000..6fb2994005 --- /dev/null +++ b/node/src/contract_core/runtime/proof_of_stake_internal.rs @@ -0,0 +1,215 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Write, +}; + +use crate::contract_shared::stored_value::StoredValue; +use crate::contract_storage::global_state::StateReader; +use types::proof_of_stake::{ + MintProvider, ProofOfStake, Queue, QueueProvider, RuntimeProvider, Stakes, StakesProvider, +}; +use types::{ + account::AccountHash, bytesrepr::ToBytes, system_contract_errors::pos::Error, ApiError, + BlockTime, CLValue, Key, Phase, TransferredTo, URef, U512, +}; + +use crate::contract_core::{execution, runtime::Runtime}; + +const BONDING_KEY: [u8; 32] = { + let mut result = [0; 32]; + result[31] = 1; + result +}; + +const UNBONDING_KEY: [u8; 32] = { + let mut result = [0; 32]; + result[31] = 2; + result +}; + +// TODO: Update MintProvider to better handle errors +impl<'a, R> MintProvider for Runtime<'a, R> +where + R: StateReader, + R::Error: Into, +{ + fn transfer_purse_to_account( + &mut self, + source: URef, + target: AccountHash, + amount: U512, + ) -> Result { + self.transfer_from_purse_to_account(source, target, amount) + .expect("should transfer from purse to account") + } + + fn transfer_purse_to_purse( + &mut self, + source: URef, + target: URef, + amount: U512, + ) -> Result<(), ()> { + let mint_contract_key = self.get_mint_contract(); + if self + .mint_transfer(mint_contract_key, source, target, amount) + .is_ok() + { + Ok(()) + } else { + Err(()) + } + } + + fn balance(&mut self, purse: URef) -> Option { + self.get_balance(purse).expect("should get balance") + } +} + +// TODO: Update QueueProvider to better handle errors +impl<'a, R> QueueProvider for Runtime<'a, R> +where + R: StateReader, + R::Error: Into, +{ + fn read_bonding(&mut self) -> Queue { + let key = BONDING_KEY.to_bytes().expect("should serialize"); + match self.context.read_ls(&key) { + Ok(Some(cl_value)) => cl_value.into_t().expect("should convert"), + _ => Queue::default(), + } + } + + fn read_unbonding(&mut self) -> Queue { + let key = UNBONDING_KEY.to_bytes().expect("should serialize"); + match self.context.read_ls(&key) { + Ok(Some(cl_value)) => cl_value.into_t().expect("should convert"), + _ => Queue::default(), + } + } + + fn write_bonding(&mut self, queue: Queue) { + let key = BONDING_KEY.to_bytes().expect("should serialize"); + let value = CLValue::from_t(queue).expect("should convert"); + self.context + .write_ls(&key, value) + .expect("should write local state") + } + + fn write_unbonding(&mut self, queue: Queue) { + let key = UNBONDING_KEY.to_bytes().expect("should serialize"); + let value = CLValue::from_t(queue).expect("should convert"); + self.context + .write_ls(&key, value) + .expect("should write local state") + } +} + +// TODO: Update RuntimeProvider to better handle errors +impl<'a, R> RuntimeProvider for Runtime<'a, R> +where + R: StateReader, + R::Error: Into, +{ + fn get_key(&self, name: &str) -> Option { + self.context.named_keys_get(name).cloned() + } + + fn put_key(&mut self, name: &str, key: Key) { + self.context + .put_key(name.to_string(), key) + .expect("should put key") + } + + fn remove_key(&mut self, name: &str) { + self.context.remove_key(name).expect("should remove key") + } + + fn get_phase(&self) -> Phase { + self.context.phase() + } + + fn get_block_time(&self) -> BlockTime { + self.context.get_blocktime() + } + + fn get_caller(&self) -> AccountHash { + self.context.get_caller() + } +} + +impl<'a, R> StakesProvider for Runtime<'a, R> +where + R: StateReader, + R::Error: Into, +{ + fn read(&self) -> Result { + let mut stakes = BTreeMap::new(); + for (name, _) in self.context.named_keys().iter() { + let mut split_name = name.split('_'); + if Some("v") != split_name.next() { + continue; + } + let hex_key = split_name + .next() + .ok_or(Error::StakesKeyDeserializationFailed)?; + if hex_key.len() != 64 { + return Err(Error::StakesKeyDeserializationFailed); + } + let mut key_bytes = [0u8; 32]; + let _bytes_written = base16::decode_slice(hex_key, &mut key_bytes) + .map_err(|_| Error::StakesKeyDeserializationFailed)?; + debug_assert!(_bytes_written == key_bytes.len()); + let pub_key = AccountHash::new(key_bytes); + let balance = split_name + .next() + .and_then(|b| U512::from_dec_str(b).ok()) + .ok_or(Error::StakesDeserializationFailed)?; + stakes.insert(pub_key, balance); + } + if stakes.is_empty() { + return Err(Error::StakesNotFound); + } + Ok(Stakes(stakes)) + } + + fn write(&mut self, stakes: &Stakes) { + // Encode the stakes as a set of uref names. + let mut new_urefs: BTreeSet = stakes + .0 + .iter() + .map(|(pub_key, balance)| { + let key_bytes = pub_key.value(); + let mut hex_key = String::with_capacity(64); + for byte in &key_bytes[..32] { + write!(hex_key, "{:02x}", byte).expect("Writing to a string cannot fail"); + } + let mut uref = String::new(); + uref.write_fmt(format_args!("v_{}_{}", hex_key, balance)) + .expect("Writing to a string cannot fail"); + uref + }) + .collect(); + // Remove and add urefs to update the contract's known urefs accordingly. + let mut removes = Vec::new(); + for (name, _) in self.context.named_keys().iter() { + if name.starts_with("v_") && !new_urefs.remove(name) { + removes.push(name.to_owned()) + } + } + for name in removes.iter() { + self.context.remove_key(name).expect("should remove key") + } + for name in new_urefs { + self.context + .put_key(name, Key::Hash([0; 32])) + .expect("should put key") + } + } +} + +impl<'a, R> ProofOfStake for Runtime<'a, R> +where + R: StateReader, + R::Error: Into, +{ +} diff --git a/node/src/contract_core/runtime/scoped_instrumenter.rs b/node/src/contract_core/runtime/scoped_instrumenter.rs new file mode 100644 index 0000000000..be7e07fac6 --- /dev/null +++ b/node/src/contract_core/runtime/scoped_instrumenter.rs @@ -0,0 +1,152 @@ +use std::{ + collections::BTreeMap, + mem, + time::{Duration, Instant}, +}; + +use crate::contract_shared::logging::log_host_function_metrics; + +use crate::contract_core::resolvers::v1_function_index::FunctionIndex; + +enum PauseState { + NotStarted, + Started(Instant), + Completed(Duration), +} + +impl PauseState { + fn new() -> Self { + PauseState::NotStarted + } + + fn activate(&mut self) { + match self { + PauseState::NotStarted => { + *self = PauseState::Started(Instant::now()); + } + _ => panic!("PauseState must be NotStarted"), + } + } + + fn complete(&mut self) { + match self { + PauseState::Started(start) => { + *self = PauseState::Completed(start.elapsed()); + } + _ => panic!("Pause must already be active"), + } + } + + fn duration(&self) -> Duration { + match self { + PauseState::NotStarted => Duration::default(), + PauseState::Completed(duration) => *duration, + PauseState::Started(start) => start.elapsed(), + } + } +} + +pub(super) struct ScopedInstrumenter { + start: Instant, + pause_state: PauseState, + function_index: FunctionIndex, + properties: BTreeMap<&'static str, String>, +} + +impl ScopedInstrumenter { + pub(super) fn new(function_index: FunctionIndex) -> Self { + ScopedInstrumenter { + start: Instant::now(), + pause_state: PauseState::new(), + function_index, + properties: BTreeMap::new(), + } + } + + pub(super) fn add_property(&mut self, key: &'static str, value: T) { + assert!(self.properties.insert(key, value.to_string()).is_none()); + } + + /// Can be called once only to effectively pause the running timer. `unpause` can likewise be + /// called once if the timer has already been paused. + pub(super) fn pause(&mut self) { + self.pause_state.activate(); + } + + pub(super) fn unpause(&mut self) { + self.pause_state.complete(); + } + + fn duration(&self) -> Duration { + self.start.elapsed() - self.pause_state.duration() + } +} + +impl Drop for ScopedInstrumenter { + fn drop(&mut self) { + let duration = self.duration(); + let host_function = match self.function_index { + FunctionIndex::GasFuncIndex => return, + FunctionIndex::WriteFuncIndex => "host_function_write", + FunctionIndex::WriteLocalFuncIndex => "host_function_write_local", + FunctionIndex::ReadFuncIndex => "host_function_read_value", + FunctionIndex::ReadLocalFuncIndex => "host_function_read_value_local", + FunctionIndex::AddFuncIndex => "host_function_add", + FunctionIndex::NewFuncIndex => "host_function_new_uref", + FunctionIndex::RetFuncIndex => "host_function_ret", + FunctionIndex::CallContractFuncIndex => "host_function_call_contract", + FunctionIndex::GetKeyFuncIndex => "host_function_get_key", + FunctionIndex::HasKeyFuncIndex => "host_function_has_key", + FunctionIndex::PutKeyFuncIndex => "host_function_put_key", + FunctionIndex::IsValidURefFnIndex => "host_function_is_valid_uref", + FunctionIndex::RevertFuncIndex => "host_function_revert", + FunctionIndex::AddAssociatedKeyFuncIndex => "host_function_add_associated_key", + FunctionIndex::RemoveAssociatedKeyFuncIndex => "host_function_remove_associated_key", + FunctionIndex::UpdateAssociatedKeyFuncIndex => "host_function_update_associated_key", + FunctionIndex::SetActionThresholdFuncIndex => "host_function_set_action_threshold", + FunctionIndex::LoadNamedKeysFuncIndex => "host_function_load_named_keys", + FunctionIndex::RemoveKeyFuncIndex => "host_function_remove_key", + FunctionIndex::GetCallerIndex => "host_function_get_caller", + FunctionIndex::GetBlocktimeIndex => "host_function_get_blocktime", + FunctionIndex::CreatePurseIndex => "host_function_create_purse", + FunctionIndex::TransferToAccountIndex => "host_function_transfer_to_account", + FunctionIndex::TransferFromPurseToAccountIndex => { + "host_function_transfer_from_purse_to_account" + } + FunctionIndex::TransferFromPurseToPurseIndex => { + "host_function_transfer_from_purse_to_purse" + } + FunctionIndex::GetBalanceIndex => "host_function_get_balance", + FunctionIndex::GetPhaseIndex => "host_function_get_phase", + FunctionIndex::GetSystemContractIndex => "host_function_get_system_contract", + FunctionIndex::GetMainPurseIndex => "host_function_get_main_purse", + FunctionIndex::ReadHostBufferIndex => "host_function_read_host_buffer", + FunctionIndex::CreateContractPackageAtHash => { + "host_function_create_contract_package_at_hash" + } + FunctionIndex::AddContractVersion => "host_function_add_contract_version", + FunctionIndex::DisableContractVersion => "host_remove_contract_version", + FunctionIndex::CallVersionedContract => "host_call_versioned_contract", + FunctionIndex::CreateContractUserGroup => "create_contract_user_group", + #[cfg(feature = "test-support")] + FunctionIndex::PrintIndex => "host_function_print", + FunctionIndex::GetRuntimeArgsizeIndex => "host_get_named_arg_size", + FunctionIndex::GetRuntimeArgIndex => "host_get_named_arg", + FunctionIndex::RemoveContractUserGroupIndex => "host_remove_contract_user_group", + FunctionIndex::ExtendContractUserGroupURefsIndex => { + "host_provision_contract_user_group_uref" + } + FunctionIndex::RemoveContractUserGroupURefsIndex => { + "host_remove_contract_user_group_urefs" + } + }; + + let mut properties = mem::take(&mut self.properties); + properties.insert( + "duration_in_seconds", + format!("{:.06e}", duration.as_secs_f64()), + ); + + log_host_function_metrics(host_function, properties); + } +} diff --git a/node/src/contract_core/runtime/standard_payment_internal.rs b/node/src/contract_core/runtime/standard_payment_internal.rs new file mode 100644 index 0000000000..f9820126c5 --- /dev/null +++ b/node/src/contract_core/runtime/standard_payment_internal.rs @@ -0,0 +1,76 @@ +use crate::contract_shared::stored_value::StoredValue; +use crate::contract_storage::global_state::StateReader; +use types::standard_payment::{ + AccountProvider, MintProvider, ProofOfStakeProvider, StandardPayment, +}; +use types::{system_contract_errors, ApiError, Key, RuntimeArgs, URef, U512}; + +use crate::contract_core::{execution, runtime::Runtime}; + +pub(crate) const METHOD_GET_PAYMENT_PURSE: &str = "get_payment_purse"; + +impl<'a, R> AccountProvider for Runtime<'a, R> +where + R: StateReader, + R::Error: Into, +{ + fn get_main_purse(&self) -> Result { + self.context + .get_main_purse() + .map_err(|_| ApiError::InvalidPurse) + } +} + +impl<'a, R> MintProvider for Runtime<'a, R> +where + R: StateReader, + R::Error: Into, +{ + fn transfer_purse_to_purse( + &mut self, + source: URef, + target: URef, + amount: U512, + ) -> Result<(), ApiError> { + let mint_contract_hash = self.get_mint_contract(); + self.mint_transfer(mint_contract_hash, source, target, amount) + .map_err(|error| match error { + execution::Error::SystemContract(system_contract_errors::Error::Mint( + mint_error, + )) => ApiError::from(mint_error), + _ => ApiError::Unhandled, + }) + } +} + +impl<'a, R> ProofOfStakeProvider for Runtime<'a, R> +where + R: StateReader, + R::Error: Into, +{ + fn get_payment_purse(&mut self) -> Result { + let pos_contract_hash = self.get_pos_contract(); + + let cl_value = self + .call_contract( + pos_contract_hash, + METHOD_GET_PAYMENT_PURSE, + RuntimeArgs::new(), + ) + .map_err(|_| { + ApiError::ProofOfStake( + system_contract_errors::pos::Error::PaymentPurseNotFound as u8, + ) + })?; + + let payment_purse_ref: URef = cl_value.into_t()?; + Ok(payment_purse_ref) + } +} + +impl<'a, R> StandardPayment for Runtime<'a, R> +where + R: StateReader, + R::Error: Into, +{ +} diff --git a/node/src/contract_core/runtime_context/mod.rs b/node/src/contract_core/runtime_context/mod.rs new file mode 100644 index 0000000000..5b904084b7 --- /dev/null +++ b/node/src/contract_core/runtime_context/mod.rs @@ -0,0 +1,857 @@ +use std::{ + cell::RefCell, + collections::{BTreeSet, HashMap, HashSet}, + convert::{TryFrom, TryInto}, + fmt::Debug, + rc::Rc, +}; + +use blake2::{ + digest::{Input, VariableOutput}, + VarBlake2b, +}; + +use crate::contract_shared::{ + account::Account, gas::Gas, newtypes::CorrelationId, stored_value::StoredValue, +}; +use crate::contract_storage::{global_state::StateReader, protocol_data::ProtocolData}; +use types::{ + account::{ + AccountHash, ActionType, AddKeyFailure, RemoveKeyFailure, SetThresholdFailure, + UpdateKeyFailure, Weight, + }, + bytesrepr, + contracts::NamedKeys, + AccessRights, BlockTime, CLType, CLValue, Contract, ContractPackage, ContractPackageHash, + EntryPointAccess, EntryPointType, Key, Phase, ProtocolVersion, RuntimeArgs, URef, + KEY_HASH_LENGTH, +}; + +use crate::contract_core::{ + engine_state::execution_effect::ExecutionEffect, + execution::{AddressGenerator, Error}, + tracking_copy::{AddResult, TrackingCopy}, + Address, +}; + +#[cfg(test)] +mod tests; + +/// Checks whether given uref has enough access rights. +pub(crate) fn uref_has_access_rights( + uref: &URef, + access_rights: &HashMap>, +) -> bool { + if let Some(known_rights) = access_rights.get(&uref.addr()) { + let new_rights = uref.access_rights(); + // check if we have sufficient access rights + known_rights + .iter() + .any(|right| *right & new_rights == new_rights) + } else { + // URef is not known + false + } +} + +pub fn validate_entry_point_access_with( + contract_package: &ContractPackage, + access: &EntryPointAccess, + validator: impl Fn(&URef) -> bool, +) -> Result<(), Error> { + if let EntryPointAccess::Groups(groups) = access { + if groups.is_empty() { + // Exits early in a special case of empty list of groups regardless of the group + // checking logic below it. + return Err(Error::InvalidContext); + } + + let find_result = groups.iter().find(|g| { + contract_package + .groups() + .get(g) + .and_then(|set| set.iter().find(|u| validator(u))) + .is_some() + }); + + if find_result.is_none() { + return Err(Error::InvalidContext); + } + } + Ok(()) +} + +/// Holds information specific to the deployed contract. +pub struct RuntimeContext<'a, R> { + tracking_copy: Rc>>, + // Enables look up of specific uref based on human-readable name + named_keys: &'a mut NamedKeys, + // Used to check uref is known before use (prevents forging urefs) + access_rights: HashMap>, + // Original account for read only tasks taken before execution + account: &'a Account, + args: RuntimeArgs, + authorization_keys: BTreeSet, + // Key pointing to the entity we are currently running + //(could point at an account or contract in the global state) + base_key: Key, + blocktime: BlockTime, + deploy_hash: [u8; KEY_HASH_LENGTH], + gas_limit: Gas, + gas_counter: Gas, + hash_address_generator: Rc>, + uref_address_generator: Rc>, + protocol_version: ProtocolVersion, + correlation_id: CorrelationId, + phase: Phase, + protocol_data: ProtocolData, + entry_point_type: EntryPointType, +} + +impl<'a, R> RuntimeContext<'a, R> +where + R: StateReader, + R::Error: Into, +{ + #[allow(clippy::too_many_arguments)] + pub fn new( + tracking_copy: Rc>>, + entry_point_type: EntryPointType, + named_keys: &'a mut NamedKeys, + access_rights: HashMap>, + runtime_args: RuntimeArgs, + authorization_keys: BTreeSet, + account: &'a Account, + base_key: Key, + blocktime: BlockTime, + deploy_hash: [u8; KEY_HASH_LENGTH], + gas_limit: Gas, + gas_counter: Gas, + hash_address_generator: Rc>, + uref_address_generator: Rc>, + protocol_version: ProtocolVersion, + correlation_id: CorrelationId, + phase: Phase, + protocol_data: ProtocolData, + ) -> Self { + RuntimeContext { + tracking_copy, + entry_point_type, + named_keys, + access_rights, + args: runtime_args, + account, + authorization_keys, + blocktime, + deploy_hash, + base_key, + gas_limit, + gas_counter, + hash_address_generator, + uref_address_generator, + protocol_version, + correlation_id, + phase, + protocol_data, + } + } + + pub fn authorization_keys(&self) -> &BTreeSet { + &self.authorization_keys + } + + pub fn named_keys_get(&self, name: &str) -> Option<&Key> { + self.named_keys.get(name) + } + + pub fn named_keys(&self) -> &NamedKeys { + &self.named_keys + } + + pub fn named_keys_mut(&mut self) -> &mut NamedKeys { + &mut self.named_keys + } + + pub fn named_keys_contains_key(&self, name: &str) -> bool { + self.named_keys.contains_key(name) + } + + // Helper function to avoid duplication in `remove_uref`. + fn remove_key_from_contract( + &mut self, + key: Key, + mut contract: Contract, + name: &str, + ) -> Result<(), Error> { + if contract.remove_named_key(name).is_none() { + return Ok(()); + } + let contract_value = StoredValue::Contract(contract); + self.tracking_copy.borrow_mut().write(key, contract_value); + Ok(()) + } + + /// Remove Key from the `named_keys` map of the current context. + /// It removes both from the ephemeral map (RuntimeContext::named_keys) but + /// also persistable map (one that is found in the + /// TrackingCopy/GlobalState). + pub fn remove_key(&mut self, name: &str) -> Result<(), Error> { + match self.base_key() { + account_hash @ Key::Account(_) => { + let account: Account = { + let mut account: Account = self.read_gs_typed(&account_hash)?; + account.named_keys_mut().remove(name); + account + }; + self.named_keys.remove(name); + let account_value = self.account_to_validated_value(account)?; + self.tracking_copy + .borrow_mut() + .write(account_hash, account_value); + Ok(()) + } + contract_uref @ Key::URef(_) => { + let contract: Contract = { + let value: StoredValue = self + .tracking_copy + .borrow_mut() + .read(self.correlation_id, &contract_uref) + .map_err(Into::into)? + .ok_or_else(|| Error::KeyNotFound(contract_uref))?; + + value.try_into().map_err(Error::TypeMismatch)? + }; + + self.named_keys.remove(name); + self.remove_key_from_contract(contract_uref, contract, name) + } + contract_hash @ Key::Hash(_) => { + let contract: Contract = self.read_gs_typed(&contract_hash)?; + self.named_keys.remove(name); + self.remove_key_from_contract(contract_hash, contract, name) + } + } + } + + pub fn get_caller(&self) -> AccountHash { + self.account.account_hash() + } + + pub fn get_blocktime(&self) -> BlockTime { + self.blocktime + } + + pub fn get_deploy_hash(&self) -> [u8; KEY_HASH_LENGTH] { + self.deploy_hash + } + + pub fn access_rights_extend(&mut self, access_rights: HashMap>) { + self.access_rights.extend(access_rights); + } + + pub fn account(&self) -> &'a Account { + &self.account + } + + pub fn args(&self) -> &RuntimeArgs { + &self.args + } + + pub fn uref_address_generator(&self) -> Rc> { + Rc::clone(&self.uref_address_generator) + } + + pub fn hash_address_generator(&self) -> Rc> { + Rc::clone(&self.hash_address_generator) + } + + pub fn state(&self) -> Rc>> { + Rc::clone(&self.tracking_copy) + } + + pub fn gas_limit(&self) -> Gas { + self.gas_limit + } + + pub fn gas_counter(&self) -> Gas { + self.gas_counter + } + + pub fn set_gas_counter(&mut self, new_gas_counter: Gas) { + self.gas_counter = new_gas_counter; + } + + pub fn base_key(&self) -> Key { + self.base_key + } + + pub fn protocol_version(&self) -> ProtocolVersion { + self.protocol_version + } + + pub fn correlation_id(&self) -> CorrelationId { + self.correlation_id + } + + pub fn phase(&self) -> Phase { + self.phase + } + + /// Generates new deterministic hash for uses as an address. + pub fn new_hash_address(&mut self) -> Result<[u8; KEY_HASH_LENGTH], Error> { + let pre_hash_bytes = self.hash_address_generator.borrow_mut().create_address(); + + let mut hasher = VarBlake2b::new(KEY_HASH_LENGTH).unwrap(); + hasher.input(&pre_hash_bytes); + let mut hash_bytes = [0; KEY_HASH_LENGTH]; + hasher.variable_result(|hash| hash_bytes.clone_from_slice(hash)); + Ok(hash_bytes) + } + + pub fn new_uref(&mut self, value: StoredValue) -> Result { + let uref = { + let addr = self.uref_address_generator.borrow_mut().create_address(); + URef::new(addr, AccessRights::READ_ADD_WRITE) + }; + let key = Key::URef(uref); + self.insert_uref(uref); + self.write_gs(key, value)?; + Ok(uref) + } + + /// Creates a new URef where the value it stores is CLType::Unit. + pub(crate) fn new_unit_uref(&mut self) -> Result { + let cl_unit = CLValue::from_components(CLType::Unit, Vec::new()); + self.new_uref(StoredValue::CLValue(cl_unit)) + } + + /// Puts `key` to the map of named keys of current context. + pub fn put_key(&mut self, name: String, key: Key) -> Result<(), Error> { + // No need to perform actual validation on the base key because an account or contract (i.e. + // the element stored under `base_key`) is allowed to add new named keys to itself. + let named_key_value = StoredValue::CLValue(CLValue::from_t((name.clone(), key))?); + self.validate_value(&named_key_value)?; + self.add_unsafe(self.base_key(), named_key_value)?; + self.insert_key(name, key); + Ok(()) + } + + pub fn read_ls(&mut self, key_bytes: &[u8]) -> Result, Error> { + let actual_length = key_bytes.len(); + if actual_length != KEY_HASH_LENGTH { + return Err(Error::InvalidKeyLength { + actual: actual_length, + expected: KEY_HASH_LENGTH, + }); + } + let hash: [u8; KEY_HASH_LENGTH] = key_bytes.try_into().unwrap(); + let key: Key = hash.into(); + let maybe_stored_value = self + .tracking_copy + .borrow_mut() + .read(self.correlation_id, &key) + .map_err(Into::into)?; + + if let Some(stored_value) = maybe_stored_value { + Ok(Some(stored_value.try_into().map_err(Error::TypeMismatch)?)) + } else { + Ok(None) + } + } + + pub fn write_ls(&mut self, key_bytes: &[u8], cl_value: CLValue) -> Result<(), Error> { + let actual_length = key_bytes.len(); + if actual_length != KEY_HASH_LENGTH { + return Err(Error::InvalidKeyLength { + actual: actual_length, + expected: KEY_HASH_LENGTH, + }); + } + let hash: [u8; KEY_HASH_LENGTH] = key_bytes.try_into().unwrap(); + self.tracking_copy + .borrow_mut() + .write(hash.into(), StoredValue::CLValue(cl_value)); + Ok(()) + } + + pub fn read_gs(&mut self, key: &Key) -> Result, Error> { + self.validate_readable(key)?; + self.validate_key(key)?; + + self.tracking_copy + .borrow_mut() + .read(self.correlation_id, key) + .map_err(Into::into) + } + + /// DO NOT EXPOSE THIS VIA THE FFI + pub fn read_gs_direct(&mut self, key: &Key) -> Result, Error> { + self.tracking_copy + .borrow_mut() + .read(self.correlation_id, key) + .map_err(Into::into) + } + + /// This method is a wrapper over `read_gs` in the sense that it extracts the type held by a + /// `StoredValue` stored in the global state in a type safe manner. + /// + /// This is useful if you want to get the exact type from global state. + pub fn read_gs_typed(&mut self, key: &Key) -> Result + where + T: TryFrom, + T::Error: Debug, + { + let value = match self.read_gs(&key)? { + None => return Err(Error::KeyNotFound(*key)), + Some(value) => value, + }; + + value.try_into().map_err(|error| { + Error::FunctionNotFound(format!( + "Type mismatch for value under {:?}: {:?}", + key, error + )) + }) + } + + pub fn write_gs(&mut self, key: Key, value: StoredValue) -> Result<(), Error> { + self.validate_writeable(&key)?; + self.validate_key(&key)?; + self.validate_value(&value)?; + self.tracking_copy.borrow_mut().write(key, value); + Ok(()) + } + + pub fn read_account(&mut self, key: &Key) -> Result, Error> { + if let Key::Account(_) = key { + self.validate_key(key)?; + self.tracking_copy + .borrow_mut() + .read(self.correlation_id, key) + .map_err(Into::into) + } else { + panic!("Do not use this function for reading from non-account keys") + } + } + + pub fn write_account(&mut self, key: Key, account: Account) -> Result<(), Error> { + if let Key::Account(_) = key { + self.validate_key(&key)?; + let account_value = self.account_to_validated_value(account)?; + self.tracking_copy.borrow_mut().write(key, account_value); + Ok(()) + } else { + panic!("Do not use this function for writing non-account keys") + } + } + + pub fn store_function( + &mut self, + contract: StoredValue, + ) -> Result<[u8; KEY_HASH_LENGTH], Error> { + self.validate_value(&contract)?; + self.new_uref(contract).map(|uref| uref.addr()) + } + + pub fn store_function_at_hash( + &mut self, + contract: StoredValue, + ) -> Result<[u8; KEY_HASH_LENGTH], Error> { + let new_hash = self.new_hash_address()?; + self.validate_value(&contract)?; + let hash_key = Key::Hash(new_hash); + self.tracking_copy.borrow_mut().write(hash_key, contract); + Ok(new_hash) + } + + pub fn insert_key(&mut self, name: String, key: Key) { + if let Key::URef(uref) = key { + self.insert_uref(uref); + } + self.named_keys.insert(name, key); + } + + pub fn insert_uref(&mut self, uref: URef) { + let rights = uref.access_rights(); + let entry = self + .access_rights + .entry(uref.addr()) + .or_insert_with(|| std::iter::empty().collect()); + entry.insert(rights); + } + + pub fn effect(&self) -> ExecutionEffect { + self.tracking_copy.borrow_mut().effect() + } + + /// Validates whether keys used in the `value` are not forged. + fn validate_value(&self, value: &StoredValue) -> Result<(), Error> { + match value { + StoredValue::CLValue(cl_value) => match cl_value.cl_type() { + CLType::Bool + | CLType::I32 + | CLType::I64 + | CLType::U8 + | CLType::U32 + | CLType::U64 + | CLType::U128 + | CLType::U256 + | CLType::U512 + | CLType::Unit + | CLType::String + | CLType::Option(_) + | CLType::List(_) + | CLType::FixedList(..) + | CLType::Result { .. } + | CLType::Map { .. } + | CLType::Tuple1(_) + | CLType::Tuple3(_) + | CLType::Any => Ok(()), + CLType::Key => { + let key: Key = cl_value.to_owned().into_t()?; // TODO: optimize? + self.validate_key(&key) + } + CLType::URef => { + let uref: URef = cl_value.to_owned().into_t()?; // TODO: optimize? + self.validate_uref(&uref) + } + tuple @ CLType::Tuple2(_) if *tuple == types::named_key_type() => { + let (_name, key): (String, Key) = cl_value.to_owned().into_t()?; // TODO: optimize? + self.validate_key(&key) + } + CLType::Tuple2(_) => Ok(()), + }, + StoredValue::Account(account) => { + // This should never happen as accounts can't be created by contracts. + // I am putting this here for the sake of completeness. + account + .named_keys() + .values() + .try_for_each(|key| self.validate_key(key)) + } + StoredValue::ContractWasm(_) => Ok(()), + StoredValue::Contract(contract_header) => contract_header + .named_keys() + .values() + .try_for_each(|key| self.validate_key(key)), + // TODO: anything to validate here? + StoredValue::ContractPackage(_) => Ok(()), + } + } + + /// Validates whether key is not forged (whether it can be found in the + /// `named_keys`) and whether the version of a key that contract wants + /// to use, has access rights that are less powerful than access rights' + /// of the key in the `named_keys`. + pub fn validate_key(&self, key: &Key) -> Result<(), Error> { + let uref = match key { + Key::URef(uref) => uref, + _ => return Ok(()), + }; + self.validate_uref(uref) + } + + pub fn validate_uref(&self, uref: &URef) -> Result<(), Error> { + if self.account.main_purse().addr() == uref.addr() { + // If passed uref matches account's purse then we have to also validate their + // access rights. + let rights = self.account.main_purse().access_rights(); + let uref_rights = uref.access_rights(); + // Access rights of the passed uref, and the account's purse should match + if rights & uref_rights == uref_rights { + return Ok(()); + } + } + + // Check if the `key` is known + if uref_has_access_rights(uref, &self.access_rights) { + Ok(()) + } else { + Err(Error::ForgedReference(*uref)) + } + } + + pub fn deserialize_keys(&self, bytes: Vec) -> Result, Error> { + let keys: Vec = bytesrepr::deserialize(bytes)?; + keys.iter().try_for_each(|k| self.validate_key(k))?; + Ok(keys) + } + + pub fn deserialize_urefs(&self, bytes: Vec) -> Result, Error> { + let keys: Vec = bytesrepr::deserialize(bytes)?; + keys.iter().try_for_each(|k| self.validate_uref(k))?; + Ok(keys) + } + + fn validate_readable(&self, key: &Key) -> Result<(), Error> { + if self.is_readable(&key) { + Ok(()) + } else { + Err(Error::InvalidAccess { + required: AccessRights::READ, + }) + } + } + + fn validate_addable(&self, key: &Key) -> Result<(), Error> { + if self.is_addable(&key) { + Ok(()) + } else { + Err(Error::InvalidAccess { + required: AccessRights::ADD, + }) + } + } + + fn validate_writeable(&self, key: &Key) -> Result<(), Error> { + if self.is_writeable(&key) { + Ok(()) + } else { + Err(Error::InvalidAccess { + required: AccessRights::WRITE, + }) + } + } + + /// Tests whether reading from the `key` is valid. + pub fn is_readable(&self, key: &Key) -> bool { + match key { + Key::Account(_) => &self.base_key() == key, + Key::Hash(_) => true, + Key::URef(uref) => uref.is_readable(), + } + } + + /// Tests whether addition to `key` is valid. + pub fn is_addable(&self, key: &Key) -> bool { + match key { + Key::Account(_) | Key::Hash(_) => &self.base_key() == key, + Key::URef(uref) => uref.is_addable(), + } + } + + /// Tests whether writing to `key` is valid. + pub fn is_writeable(&self, key: &Key) -> bool { + match key { + Key::Account(_) | Key::Hash(_) => false, + Key::URef(uref) => uref.is_writeable(), + } + } + + /// Adds `value` to the `key`. The premise for being able to `add` value is + /// that the type of it [value] can be added (is a Monoid). If the + /// values can't be added, either because they're not a Monoid or if the + /// value stored under `key` has different type, then `TypeMismatch` + /// errors is returned. + pub fn add_gs(&mut self, key: Key, value: StoredValue) -> Result<(), Error> { + self.validate_addable(&key)?; + self.validate_key(&key)?; + self.validate_value(&value)?; + self.add_unsafe(key, value) + } + + fn add_unsafe(&mut self, key: Key, value: StoredValue) -> Result<(), Error> { + match self + .tracking_copy + .borrow_mut() + .add(self.correlation_id, key, value) + { + Err(storage_error) => Err(storage_error.into()), + Ok(AddResult::Success) => Ok(()), + Ok(AddResult::KeyNotFound(key)) => Err(Error::KeyNotFound(key)), + Ok(AddResult::TypeMismatch(type_mismatch)) => Err(Error::TypeMismatch(type_mismatch)), + Ok(AddResult::Serialization(error)) => Err(Error::BytesRepr(error)), + } + } + + pub fn add_associated_key( + &mut self, + account_hash: AccountHash, + weight: Weight, + ) -> Result<(), Error> { + // Check permission to modify associated keys + if !self.is_valid_context() { + // Exit early with error to avoid mutations + return Err(AddKeyFailure::PermissionDenied.into()); + } + + if !self + .account() + .can_manage_keys_with(&self.authorization_keys) + { + // Exit early if authorization keys weight doesn't exceed required + // key management threshold + return Err(AddKeyFailure::PermissionDenied.into()); + } + + // Converts an account's public key into a URef + let key = Key::Account(self.account().account_hash()); + + // Take an account out of the global state + let account = { + let mut account: Account = self.read_gs_typed(&key)?; + // Exit early in case of error without updating global state + account + .add_associated_key(account_hash, weight) + .map_err(Error::from)?; + account + }; + + let account_value = self.account_to_validated_value(account)?; + + self.tracking_copy.borrow_mut().write(key, account_value); + + Ok(()) + } + + pub fn remove_associated_key(&mut self, account_hash: AccountHash) -> Result<(), Error> { + // Check permission to modify associated keys + if !self.is_valid_context() { + // Exit early with error to avoid mutations + return Err(RemoveKeyFailure::PermissionDenied.into()); + } + + if !self + .account() + .can_manage_keys_with(&self.authorization_keys) + { + // Exit early if authorization keys weight doesn't exceed required + // key management threshold + return Err(RemoveKeyFailure::PermissionDenied.into()); + } + + // Converts an account's public key into a URef + let key = Key::Account(self.account().account_hash()); + + // Take an account out of the global state + let mut account: Account = self.read_gs_typed(&key)?; + + // Exit early in case of error without updating global state + account + .remove_associated_key(account_hash) + .map_err(Error::from)?; + + let account_value = self.account_to_validated_value(account)?; + + self.tracking_copy.borrow_mut().write(key, account_value); + + Ok(()) + } + + pub fn update_associated_key( + &mut self, + account_hash: AccountHash, + weight: Weight, + ) -> Result<(), Error> { + // Check permission to modify associated keys + if !self.is_valid_context() { + // Exit early with error to avoid mutations + return Err(UpdateKeyFailure::PermissionDenied.into()); + } + + if !self + .account() + .can_manage_keys_with(&self.authorization_keys) + { + // Exit early if authorization keys weight doesn't exceed required + // key management threshold + return Err(UpdateKeyFailure::PermissionDenied.into()); + } + + // Converts an account's public key into a URef + let key = Key::Account(self.account().account_hash()); + + // Take an account out of the global state + let mut account: Account = self.read_gs_typed(&key)?; + + // Exit early in case of error without updating global state + account + .update_associated_key(account_hash, weight) + .map_err(Error::from)?; + + let account_value = self.account_to_validated_value(account)?; + + self.tracking_copy.borrow_mut().write(key, account_value); + + Ok(()) + } + + pub fn set_action_threshold( + &mut self, + action_type: ActionType, + threshold: Weight, + ) -> Result<(), Error> { + // Check permission to modify associated keys + if !self.is_valid_context() { + // Exit early with error to avoid mutations + return Err(SetThresholdFailure::PermissionDeniedError.into()); + } + + if !self + .account() + .can_manage_keys_with(&self.authorization_keys) + { + // Exit early if authorization keys weight doesn't exceed required + // key management threshold + return Err(SetThresholdFailure::PermissionDeniedError.into()); + } + + // Converts an account's public key into a URef + let key = Key::Account(self.account().account_hash()); + + // Take an account out of the global state + let mut account: Account = self.read_gs_typed(&key)?; + + // Exit early in case of error without updating global state + account + .set_action_threshold(action_type, threshold) + .map_err(Error::from)?; + + let account_value = self.account_to_validated_value(account)?; + + self.tracking_copy.borrow_mut().write(key, account_value); + + Ok(()) + } + + pub fn protocol_data(&self) -> ProtocolData { + self.protocol_data + } + + /// Creates validated instance of `StoredValue` from `account`. + fn account_to_validated_value(&self, account: Account) -> Result { + let value = StoredValue::Account(account); + self.validate_value(&value)?; + Ok(value) + } + + /// Checks if the account context is valid. + fn is_valid_context(&self) -> bool { + self.base_key() == Key::Account(self.account().account_hash()) + } + + /// Gets main purse id + pub fn get_main_purse(&self) -> Result { + if !self.is_valid_context() { + return Err(Error::InvalidContext); + } + Ok(self.account().main_purse()) + } + + /// Gets entry point type. + pub fn entry_point_type(&self) -> EntryPointType { + self.entry_point_type + } + + /// Gets given contract package with its access_key validated against current context. + pub(crate) fn get_validated_contract_package( + &mut self, + package_hash: ContractPackageHash, + ) -> Result { + let package_hash_key = Key::from(package_hash); + self.validate_key(&package_hash_key)?; + let contract_package: ContractPackage = self.read_gs_typed(&Key::from(package_hash))?; + self.validate_uref(&contract_package.access_key())?; + Ok(contract_package) + } +} diff --git a/node/src/contract_core/runtime_context/tests.rs b/node/src/contract_core/runtime_context/tests.rs new file mode 100644 index 0000000000..6c0fff5bc8 --- /dev/null +++ b/node/src/contract_core/runtime_context/tests.rs @@ -0,0 +1,833 @@ +use std::{ + cell::RefCell, + collections::{BTreeSet, HashMap, HashSet}, + iter::{self, FromIterator}, + rc::Rc, +}; + +use rand::RngCore; + +use crate::contract_shared::{ + account::{Account, AssociatedKeys}, + additive_map::AdditiveMap, + gas::Gas, + newtypes::CorrelationId, + stored_value::StoredValue, + transform::Transform, +}; +use crate::contract_storage::global_state::{ + in_memory::{InMemoryGlobalState, InMemoryGlobalStateView}, + CommitResult, StateProvider, +}; +use types::{ + account::{ + AccountHash, ActionType, AddKeyFailure, RemoveKeyFailure, SetThresholdFailure, Weight, + }, + contracts::NamedKeys, + AccessRights, BlockTime, CLValue, Contract, EntryPointType, EntryPoints, Key, Phase, + ProtocolVersion, RuntimeArgs, URef, KEY_HASH_LENGTH, +}; + +use super::{Address, Error, RuntimeContext}; +use crate::contract_core::{ + execution::AddressGenerator, runtime::extract_access_rights_from_keys, + tracking_copy::TrackingCopy, +}; + +const DEPLOY_HASH: [u8; 32] = [1u8; 32]; +const PHASE: Phase = Phase::Session; + +fn mock_tracking_copy( + init_key: Key, + init_account: Account, +) -> TrackingCopy { + let correlation_id = CorrelationId::new(); + let hist = InMemoryGlobalState::empty().unwrap(); + let root_hash = hist.empty_root_hash; + let transform = Transform::Write(StoredValue::Account(init_account)); + + let mut m = AdditiveMap::new(); + m.insert(init_key, transform); + let commit_result = hist + .commit(correlation_id, root_hash, m) + .expect("Creation of mocked account should be a success."); + + let new_hash = match commit_result { + CommitResult::Success { state_root, .. } => state_root, + other => panic!("Commiting changes to test History failed: {:?}.", other), + }; + + let reader = hist + .checkout(new_hash) + .expect("Checkout should not throw errors.") + .expect("Root hash should exist."); + + TrackingCopy::new(reader) +} + +fn mock_account_with_purse(account_hash: AccountHash, purse: [u8; 32]) -> (Key, Account) { + let associated_keys = AssociatedKeys::new(account_hash, Weight::new(1)); + let account = Account::new( + account_hash, + NamedKeys::new(), + URef::new(purse, AccessRights::READ_ADD_WRITE), + associated_keys, + Default::default(), + ); + let key = Key::Account(account_hash); + + (key, account) +} + +fn mock_account(account_hash: AccountHash) -> (Key, Account) { + mock_account_with_purse(account_hash, [0; 32]) +} + +// create random account key. +fn random_account_key(entropy_source: &mut G) -> Key { + let mut key = [0u8; 32]; + entropy_source.fill_bytes(&mut key); + Key::Account(AccountHash::new(key)) +} + +// create random contract key. +fn random_contract_key(entropy_source: &mut G) -> Key { + let mut key = [0u8; 32]; + entropy_source.fill_bytes(&mut key); + Key::Hash(key) +} + +// Create URef Key. +fn create_uref(address_generator: &mut AddressGenerator, rights: AccessRights) -> Key { + let address = address_generator.create_address(); + Key::URef(URef::new(address, rights)) +} + +fn random_hash(entropy_source: &mut G) -> Key { + let mut key = [0u8; KEY_HASH_LENGTH]; + entropy_source.fill_bytes(&mut key); + Key::Hash(key) +} + +fn mock_runtime_context<'a>( + account: &'a Account, + base_key: Key, + named_keys: &'a mut NamedKeys, + access_rights: HashMap>, + hash_address_generator: AddressGenerator, + uref_address_generator: AddressGenerator, +) -> RuntimeContext<'a, InMemoryGlobalStateView> { + let tracking_copy = mock_tracking_copy(base_key, account.clone()); + RuntimeContext::new( + Rc::new(RefCell::new(tracking_copy)), + EntryPointType::Session, + named_keys, + access_rights, + RuntimeArgs::new(), + BTreeSet::from_iter(vec![AccountHash::new([0; 32])]), + &account, + base_key, + BlockTime::new(0), + [1u8; 32], + Gas::default(), + Gas::default(), + Rc::new(RefCell::new(hash_address_generator)), + Rc::new(RefCell::new(uref_address_generator)), + ProtocolVersion::V1_0_0, + CorrelationId::new(), + Phase::Session, + Default::default(), + ) +} + +#[allow(clippy::assertions_on_constants)] +fn assert_forged_reference(result: Result) { + match result { + Err(Error::ForgedReference(_)) => assert!(true), + _ => panic!("Error. Test should have failed with ForgedReference error but didn't."), + } +} + +#[allow(clippy::assertions_on_constants)] +fn assert_invalid_access(result: Result, expecting: AccessRights) { + match result { + Err(Error::InvalidAccess { required }) if required == expecting => assert!(true), + other => panic!( + "Error. Test should have failed with InvalidAccess error but didn't: {:?}.", + other + ), + } +} + +fn test(access_rights: HashMap>, query: F) -> Result +where + F: FnOnce(RuntimeContext) -> Result, +{ + let deploy_hash = [1u8; 32]; + let (base_key, account) = mock_account(AccountHash::new([0u8; 32])); + + let mut named_keys = NamedKeys::new(); + let uref_address_generator = AddressGenerator::new(&deploy_hash, Phase::Session); + let hash_address_generator = AddressGenerator::new(&deploy_hash, Phase::Session); + let runtime_context = mock_runtime_context( + &account, + base_key, + &mut named_keys, + access_rights, + hash_address_generator, + uref_address_generator, + ); + query(runtime_context) +} + +#[test] +fn use_uref_valid() { + // Test fixture + let mut rng = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let uref = create_uref(&mut rng, AccessRights::READ_WRITE); + let access_rights = extract_access_rights_from_keys(vec![uref]); + // Use uref as the key to perform an action on the global state. + // This should succeed because the uref is valid. + let value = StoredValue::CLValue(CLValue::from_t(43_i32).unwrap()); + let query_result = test(access_rights, |mut rc| rc.write_gs(uref, value)); + query_result.expect("writing using valid uref should succeed"); +} + +#[test] +fn use_uref_forged() { + // Test fixture + let mut rng = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let uref = create_uref(&mut rng, AccessRights::READ_WRITE); + let access_rights = HashMap::new(); + let value = StoredValue::CLValue(CLValue::from_t(43_i32).unwrap()); + let query_result = test(access_rights, |mut rc| rc.write_gs(uref, value)); + + assert_forged_reference(query_result); +} + +#[test] +fn account_key_not_writeable() { + let mut rng = rand::thread_rng(); + let acc_key = random_account_key(&mut rng); + let query_result = test(HashMap::new(), |mut rc| { + rc.write_gs( + acc_key, + StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()), + ) + }); + assert_invalid_access(query_result, AccessRights::WRITE); +} + +#[test] +fn account_key_readable_valid() { + // Account key is readable if it is a "base" key - current context of the + // execution. + let query_result = test(HashMap::new(), |mut rc| { + let base_key = rc.base_key(); + + let result = rc + .read_gs(&base_key) + .expect("Account key is readable.") + .expect("Account is found in GS."); + + assert_eq!(result, StoredValue::Account(rc.account().clone())); + Ok(()) + }); + + assert!(query_result.is_ok()); +} + +#[test] +fn account_key_readable_invalid() { + // Account key is NOT readable if it is different than the "base" key. + let mut rng = rand::thread_rng(); + let other_acc_key = random_account_key(&mut rng); + + let query_result = test(HashMap::new(), |mut rc| rc.read_gs(&other_acc_key)); + + assert_invalid_access(query_result, AccessRights::READ); +} + +#[test] +fn account_key_addable_valid() { + // Account key is addable if it is a "base" key - current context of the + // execution. + let mut rng = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let uref = create_uref(&mut rng, AccessRights::READ); + let access_rights = extract_access_rights_from_keys(vec![uref]); + let query_result = test(access_rights, |mut rc| { + let base_key = rc.base_key(); + let uref_name = "NewURef".to_owned(); + let named_key = StoredValue::CLValue(CLValue::from_t((uref_name.clone(), uref)).unwrap()); + + rc.add_gs(base_key, named_key).expect("Adding should work."); + + let named_key_transform = Transform::AddKeys(iter::once((uref_name, uref)).collect()); + + assert_eq!( + *rc.effect().transforms.get(&base_key).unwrap(), + named_key_transform + ); + + Ok(()) + }); + + assert!(query_result.is_ok()); +} + +#[test] +fn account_key_addable_invalid() { + // Account key is NOT addable if it is a "base" key - current context of the + // execution. + let mut rng = rand::thread_rng(); + let other_acc_key = random_account_key(&mut rng); + + let query_result = test(HashMap::new(), |mut rc| { + rc.add_gs( + other_acc_key, + StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()), + ) + }); + + assert_invalid_access(query_result, AccessRights::ADD); +} + +#[test] +fn contract_key_readable_valid() { + // Account key is readable if it is a "base" key - current context of the + // execution. + let mut rng = rand::thread_rng(); + let contract_key = random_contract_key(&mut rng); + let query_result = test(HashMap::new(), |mut rc| rc.read_gs(&contract_key)); + + assert!(query_result.is_ok()); +} + +#[test] +fn contract_key_not_writeable() { + // Account key is readable if it is a "base" key - current context of the + // execution. + let mut rng = rand::thread_rng(); + let contract_key = random_contract_key(&mut rng); + let query_result = test(HashMap::new(), |mut rc| { + rc.write_gs( + contract_key, + StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()), + ) + }); + + assert_invalid_access(query_result, AccessRights::WRITE); +} + +#[test] +fn contract_key_addable_valid() { + // Contract key is addable if it is a "base" key - current context of the execution. + let account_hash = AccountHash::new([0u8; 32]); + let (account_key, account) = mock_account(account_hash); + let authorization_keys = BTreeSet::from_iter(vec![account_hash]); + let hash_address_generator = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let mut uref_address_generator = AddressGenerator::new(&DEPLOY_HASH, PHASE); + + let mut rng = rand::thread_rng(); + let contract_key = random_contract_key(&mut rng); + let contract = StoredValue::Contract(Contract::default()); + + let tracking_copy = Rc::new(RefCell::new(mock_tracking_copy( + account_key, + account.clone(), + ))); + tracking_copy.borrow_mut().write(contract_key, contract); + + let mut named_keys = NamedKeys::new(); + let uref = create_uref(&mut uref_address_generator, AccessRights::WRITE); + let uref_name = "NewURef".to_owned(); + let named_uref_tuple = + StoredValue::CLValue(CLValue::from_t((uref_name.clone(), uref)).unwrap()); + + let access_rights = extract_access_rights_from_keys(vec![uref]); + + let mut runtime_context = RuntimeContext::new( + Rc::clone(&tracking_copy), + EntryPointType::Session, + &mut named_keys, + access_rights, + RuntimeArgs::new(), + authorization_keys, + &account, + contract_key, + BlockTime::new(0), + DEPLOY_HASH, + Gas::default(), + Gas::default(), + Rc::new(RefCell::new(hash_address_generator)), + Rc::new(RefCell::new(uref_address_generator)), + ProtocolVersion::V1_0_0, + CorrelationId::new(), + PHASE, + Default::default(), + ); + + runtime_context + .add_gs(contract_key, named_uref_tuple) + .expect("Adding should work."); + + let updated_contract = StoredValue::Contract(Contract::new( + [0u8; 32], + [0u8; 32], + iter::once((uref_name, uref)).collect(), + EntryPoints::default(), + ProtocolVersion::V1_0_0, + )); + + assert_eq!( + *tracking_copy + .borrow() + .effect() + .transforms + .get(&contract_key) + .unwrap(), + Transform::Write(updated_contract) + ); +} + +#[test] +fn contract_key_addable_invalid() { + let account_hash = AccountHash::new([0u8; 32]); + let (account_key, account) = mock_account(account_hash); + let authorization_keys = BTreeSet::from_iter(vec![account_hash]); + let hash_address_generator = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let mut uref_address_generator = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let mut rng = rand::thread_rng(); + let contract_key = random_contract_key(&mut rng); + + let other_contract_key = random_contract_key(&mut rng); + let contract = StoredValue::Contract(Contract::default()); + let tracking_copy = Rc::new(RefCell::new(mock_tracking_copy( + account_key, + account.clone(), + ))); + + tracking_copy.borrow_mut().write(contract_key, contract); + + let mut named_keys = NamedKeys::new(); + + let uref = create_uref(&mut uref_address_generator, AccessRights::WRITE); + let uref_name = "NewURef".to_owned(); + let named_uref_tuple = StoredValue::CLValue(CLValue::from_t((uref_name, uref)).unwrap()); + + let access_rights = extract_access_rights_from_keys(vec![uref]); + let mut runtime_context = RuntimeContext::new( + Rc::clone(&tracking_copy), + EntryPointType::Session, + &mut named_keys, + access_rights, + RuntimeArgs::new(), + authorization_keys, + &account, + other_contract_key, + BlockTime::new(0), + DEPLOY_HASH, + Gas::default(), + Gas::default(), + Rc::new(RefCell::new(hash_address_generator)), + Rc::new(RefCell::new(uref_address_generator)), + ProtocolVersion::V1_0_0, + CorrelationId::new(), + PHASE, + Default::default(), + ); + + let result = runtime_context.add_gs(contract_key, named_uref_tuple); + + assert_invalid_access(result, AccessRights::ADD); +} + +#[test] +fn uref_key_readable_valid() { + let mut rng = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let uref_key = create_uref(&mut rng, AccessRights::READ); + let access_rights = extract_access_rights_from_keys(vec![uref_key]); + let query_result = test(access_rights, |mut rc| rc.read_gs(&uref_key)); + assert!(query_result.is_ok()); +} + +#[test] +fn uref_key_readable_invalid() { + let mut rng = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let uref_key = create_uref(&mut rng, AccessRights::WRITE); + let access_rights = extract_access_rights_from_keys(vec![uref_key]); + let query_result = test(access_rights, |mut rc| rc.read_gs(&uref_key)); + assert_invalid_access(query_result, AccessRights::READ); +} + +#[test] +fn uref_key_writeable_valid() { + let mut rng = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let uref_key = create_uref(&mut rng, AccessRights::WRITE); + let access_rights = extract_access_rights_from_keys(vec![uref_key]); + let query_result = test(access_rights, |mut rc| { + rc.write_gs( + uref_key, + StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()), + ) + }); + assert!(query_result.is_ok()); +} + +#[test] +fn uref_key_writeable_invalid() { + let mut rng = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let uref_key = create_uref(&mut rng, AccessRights::READ); + let access_rights = extract_access_rights_from_keys(vec![uref_key]); + let query_result = test(access_rights, |mut rc| { + rc.write_gs( + uref_key, + StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()), + ) + }); + assert_invalid_access(query_result, AccessRights::WRITE); +} + +#[test] +fn uref_key_addable_valid() { + let mut rng = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let uref_key = create_uref(&mut rng, AccessRights::ADD_WRITE); + let access_rights = extract_access_rights_from_keys(vec![uref_key]); + let query_result = test(access_rights, |mut rc| { + rc.write_gs( + uref_key, + StoredValue::CLValue(CLValue::from_t(10_i32).unwrap()), + ) + .expect("Writing to the GlobalState should work."); + rc.add_gs( + uref_key, + StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()), + ) + }); + assert!(query_result.is_ok()); +} + +#[test] +fn uref_key_addable_invalid() { + let mut rng = AddressGenerator::new(&DEPLOY_HASH, PHASE); + let uref_key = create_uref(&mut rng, AccessRights::WRITE); + let access_rights = extract_access_rights_from_keys(vec![uref_key]); + let query_result = test(access_rights, |mut rc| { + rc.add_gs( + uref_key, + StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()), + ) + }); + assert_invalid_access(query_result, AccessRights::ADD); +} + +#[test] +fn hash_key_readable() { + // values under hash's are universally readable + let query = |runtime_context: RuntimeContext| { + let mut rng = rand::thread_rng(); + let key = random_hash(&mut rng); + runtime_context.validate_readable(&key) + }; + let query_result = test(HashMap::new(), query); + assert!(query_result.is_ok()) +} + +#[test] +fn hash_key_writeable() { + // values under hash's are immutable + let query = |runtime_context: RuntimeContext| { + let mut rng = rand::thread_rng(); + let key = random_hash(&mut rng); + runtime_context.validate_writeable(&key) + }; + let query_result = test(HashMap::new(), query); + assert!(query_result.is_err()) +} + +#[test] +fn hash_key_addable_invalid() { + // values under hashes are immutable + let query = |runtime_context: RuntimeContext| { + let mut rng = rand::thread_rng(); + let key = random_hash(&mut rng); + runtime_context.validate_addable(&key) + }; + let query_result = test(HashMap::new(), query); + assert!(query_result.is_err()) +} + +#[test] +fn manage_associated_keys() { + // Testing a valid case only - successfuly added a key, and successfuly removed, + // making sure `account_dirty` mutated + let access_rights = HashMap::new(); + let query = |mut runtime_context: RuntimeContext| { + let account_hash = AccountHash::new([42; 32]); + let weight = Weight::new(155); + + // Add a key (this doesn't check for all invariants as `add_key` + // is already tested in different place) + runtime_context + .add_associated_key(account_hash, weight) + .expect("Unable to add key"); + + let effect = runtime_context.effect(); + let transform = effect.transforms.get(&runtime_context.base_key()).unwrap(); + let account = match transform { + Transform::Write(StoredValue::Account(account)) => account, + _ => panic!("Invalid transform operation found"), + }; + account + .get_associated_key_weight(account_hash) + .expect("Account hash wasn't added to associated keys"); + + let new_weight = Weight::new(100); + runtime_context + .update_associated_key(account_hash, new_weight) + .expect("Unable to update key"); + + let effect = runtime_context.effect(); + let transform = effect.transforms.get(&runtime_context.base_key()).unwrap(); + let account = match transform { + Transform::Write(StoredValue::Account(account)) => account, + _ => panic!("Invalid transform operation found"), + }; + let value = account + .get_associated_key_weight(account_hash) + .expect("Account hash wasn't added to associated keys"); + + assert_eq!(value, &new_weight, "value was not updated"); + + // Remove a key that was already added + runtime_context + .remove_associated_key(account_hash) + .expect("Unable to remove key"); + + // Verify + let effect = runtime_context.effect(); + let transform = effect.transforms.get(&runtime_context.base_key()).unwrap(); + let account = match transform { + Transform::Write(StoredValue::Account(account)) => account, + _ => panic!("Invalid transform operation found"), + }; + + assert!(account.get_associated_key_weight(account_hash).is_none()); + + // Remove a key that was already removed + runtime_context + .remove_associated_key(account_hash) + .expect_err("A non existing key was unexpectedly removed again"); + + Ok(()) + }; + let _ = test(access_rights, query); +} + +#[test] +fn action_thresholds_management() { + // Testing a valid case only - successfuly added a key, and successfuly removed, + // making sure `account_dirty` mutated + let access_rights = HashMap::new(); + let query = |mut runtime_context: RuntimeContext| { + runtime_context + .add_associated_key(AccountHash::new([42; 32]), Weight::new(254)) + .expect("Unable to add associated key with maximum weight"); + runtime_context + .set_action_threshold(ActionType::KeyManagement, Weight::new(253)) + .expect("Unable to set action threshold KeyManagement"); + runtime_context + .set_action_threshold(ActionType::Deployment, Weight::new(252)) + .expect("Unable to set action threshold Deployment"); + + let effect = runtime_context.effect(); + let transform = effect.transforms.get(&runtime_context.base_key()).unwrap(); + let mutated_account = match transform { + Transform::Write(StoredValue::Account(account)) => account, + _ => panic!("Invalid transform operation found"), + }; + + assert_eq!( + mutated_account.action_thresholds().deployment(), + &Weight::new(252) + ); + assert_eq!( + mutated_account.action_thresholds().key_management(), + &Weight::new(253) + ); + + runtime_context + .set_action_threshold(ActionType::Deployment, Weight::new(255)) + .expect_err("Shouldn't be able to set deployment threshold higher than key management"); + + Ok(()) + }; + let _ = test(access_rights, query); +} + +#[test] +fn should_verify_ownership_before_adding_key() { + // Testing a valid case only - successfuly added a key, and successfuly removed, + // making sure `account_dirty` mutated + let access_rights = HashMap::new(); + let query = |mut runtime_context: RuntimeContext| { + // Overwrites a `base_key` to a different one before doing any operation as + // account `[0; 32]` + runtime_context.base_key = Key::Hash([1; 32]); + + let err = runtime_context + .add_associated_key(AccountHash::new([84; 32]), Weight::new(123)) + .expect_err("This operation should return error"); + + match err { + Error::AddKeyFailure(AddKeyFailure::PermissionDenied) => {} + e => panic!("Invalid error variant: {:?}", e), + } + + Ok(()) + }; + let _ = test(access_rights, query); +} + +#[test] +fn should_verify_ownership_before_removing_a_key() { + // Testing a valid case only - successfuly added a key, and successfuly removed, + // making sure `account_dirty` mutated + let access_rights = HashMap::new(); + let query = |mut runtime_context: RuntimeContext| { + // Overwrites a `base_key` to a different one before doing any operation as + // account `[0; 32]` + runtime_context.base_key = Key::Hash([1; 32]); + + let err = runtime_context + .remove_associated_key(AccountHash::new([84; 32])) + .expect_err("This operation should return error"); + + match err { + Error::RemoveKeyFailure(RemoveKeyFailure::PermissionDenied) => {} + ref e => panic!("Invalid error variant: {:?}", e), + } + + Ok(()) + }; + let _ = test(access_rights, query); +} + +#[test] +fn should_verify_ownership_before_setting_action_threshold() { + // Testing a valid case only - successfuly added a key, and successfuly removed, + // making sure `account_dirty` mutated + let access_rights = HashMap::new(); + let query = |mut runtime_context: RuntimeContext| { + // Overwrites a `base_key` to a different one before doing any operation as + // account `[0; 32]` + runtime_context.base_key = Key::Hash([1; 32]); + + let err = runtime_context + .set_action_threshold(ActionType::Deployment, Weight::new(123)) + .expect_err("This operation should return error"); + + match err { + Error::SetThresholdFailure(SetThresholdFailure::PermissionDeniedError) => {} + ref e => panic!("Invalid error variant: {:?}", e), + } + + Ok(()) + }; + let _ = test(access_rights, query); +} + +#[test] +fn can_roundtrip_key_value_pairs() { + let access_rights = HashMap::new(); + let query = |mut runtime_context: RuntimeContext| { + let mut rng = rand::thread_rng(); + let test_key = random_hash(&mut rng).into_hash().expect("should be hash"); + let test_value = CLValue::from_t("test_value".to_string()).unwrap(); + + runtime_context + .write_ls(&test_key, test_value.clone()) + .expect("should write_ls"); + + let result = runtime_context.read_ls(&test_key).expect("should read_ls"); + + Ok(result == Some(test_value)) + }; + let query_result = test(access_rights, query).expect("should be ok"); + assert!(query_result) +} + +#[test] +fn remove_uref_works() { + // Test that `remove_uref` removes Key from both ephemeral representation + // which is one of the current RuntimeContext, and also puts that change + // into the `TrackingCopy` so that it's later committed to the GlobalState. + + let access_rights = HashMap::new(); + let deploy_hash = [1u8; 32]; + let (base_key, account) = mock_account(AccountHash::new([0u8; 32])); + let hash_address_generator = AddressGenerator::new(&deploy_hash, Phase::Session); + let mut uref_address_generator = AddressGenerator::new(&deploy_hash, Phase::Session); + let uref_name = "Foo".to_owned(); + let uref_key = create_uref(&mut uref_address_generator, AccessRights::READ); + let mut named_keys = iter::once((uref_name.clone(), uref_key)).collect(); + let mut runtime_context = mock_runtime_context( + &account, + base_key, + &mut named_keys, + access_rights, + hash_address_generator, + uref_address_generator, + ); + + assert!(runtime_context.named_keys_contains_key(&uref_name)); + assert!(runtime_context.remove_key(&uref_name).is_ok()); + assert!(runtime_context.validate_key(&uref_key).is_err()); + assert!(!runtime_context.named_keys_contains_key(&uref_name)); + let effects = runtime_context.effect(); + let transform = effects.transforms.get(&base_key).unwrap(); + let account = match transform { + Transform::Write(StoredValue::Account(account)) => account, + _ => panic!("Invalid transform operation found"), + }; + assert!(!account.named_keys().contains_key(&uref_name)); +} + +#[test] +fn validate_valid_purse_of_an_account() { + // Tests that URef which matches a purse of a given context gets validated + let mock_purse = [42u8; 32]; + let access_rights = HashMap::new(); + let deploy_hash = [1u8; 32]; + let (base_key, account) = mock_account_with_purse(AccountHash::new([0u8; 32]), mock_purse); + let mut named_keys = NamedKeys::new(); + let hash_address_generator = AddressGenerator::new(&deploy_hash, Phase::Session); + let uref_address_generator = AddressGenerator::new(&deploy_hash, Phase::Session); + let runtime_context = mock_runtime_context( + &account, + base_key, + &mut named_keys, + access_rights, + hash_address_generator, + uref_address_generator, + ); + + // URef that has the same id as purse of an account gets validated + // successfully. + let purse = URef::new(mock_purse, AccessRights::READ_ADD_WRITE); + assert!(runtime_context.validate_uref(&purse).is_ok()); + + // URef that has the same id as purse of an account gets validated + // successfully as the passed purse has only subset of the privileges + let purse = URef::new(mock_purse, AccessRights::READ); + assert!(runtime_context.validate_uref(&purse).is_ok()); + let purse = URef::new(mock_purse, AccessRights::ADD); + assert!(runtime_context.validate_uref(&purse).is_ok()); + let purse = URef::new(mock_purse, AccessRights::WRITE); + assert!(runtime_context.validate_uref(&purse).is_ok()); + + // Purse ID that doesn't match account's purse should fail as it's also not + // in known urefs. + let purse = URef::new([53; 32], AccessRights::READ_ADD_WRITE); + assert!(runtime_context.validate_uref(&purse).is_err()); +} diff --git a/node/src/contract_core/tracking_copy/byte_size.rs b/node/src/contract_core/tracking_copy/byte_size.rs new file mode 100644 index 0000000000..8ac9f5f38b --- /dev/null +++ b/node/src/contract_core/tracking_copy/byte_size.rs @@ -0,0 +1,134 @@ +use std::{collections::BTreeMap, mem}; + +use crate::contract_shared::{account::Account, stored_value::StoredValue}; +use types::{bytesrepr::ToBytes, ContractWasm, Key}; + +/// Returns byte size of the element - both heap size and stack size. +pub trait ByteSize { + fn byte_size(&self) -> usize; +} + +impl ByteSize for Key { + fn byte_size(&self) -> usize { + mem::size_of::() + self.heap_size() + } +} + +impl ByteSize for Account { + fn byte_size(&self) -> usize { + mem::size_of::() + self.heap_size() + self.named_keys().byte_size() + } +} + +impl ByteSize for ContractWasm { + fn byte_size(&self) -> usize { + mem::size_of::() + self.heap_size() + } +} + +impl ByteSize for String { + fn byte_size(&self) -> usize { + mem::size_of::() + self.heap_size() + } +} + +impl ByteSize for BTreeMap { + fn byte_size(&self) -> usize { + mem::size_of::>() + + self.heap_size() + + self.len() * (mem::size_of::() + mem::size_of::()) + } +} + +impl ByteSize for StoredValue { + fn byte_size(&self) -> usize { + mem::size_of::() + + match self { + StoredValue::CLValue(cl_value) => cl_value.serialized_length(), + StoredValue::Account(account) => account.heap_size(), + StoredValue::ContractWasm(contract_wasm) => contract_wasm.heap_size(), + StoredValue::Contract(contract_header) => contract_header.serialized_length(), + StoredValue::ContractPackage(contract_package) => { + contract_package.serialized_length() + } + } + } +} + +/// Returns heap size of the value. +/// Note it's different from [ByteSize] that returns both heap and stack size. +pub trait HeapSizeOf { + fn heap_size(&self) -> usize; +} + +impl HeapSizeOf for Key { + fn heap_size(&self) -> usize { + 0 + } +} + +// TODO: contract has other fields (re a bunch) that are not repr here...on purpose? +impl HeapSizeOf for Account { + fn heap_size(&self) -> usize { + self.named_keys().heap_size() + } +} + +// TODO: contract has other fields (re protocol version) that are not repr here...on purpose? +impl HeapSizeOf for ContractWasm { + fn heap_size(&self) -> usize { + self.bytes().len() + } +} + +// NOTE: We're ignoring size of the tree's nodes. +impl HeapSizeOf for BTreeMap { + fn heap_size(&self) -> usize { + self.iter() + .fold(0, |sum, (k, v)| sum + k.heap_size() + v.heap_size()) + } +} + +impl ByteSize for [T] { + fn byte_size(&self) -> usize { + self.iter() + .fold(0, |sum, el| sum + mem::size_of::() + el.heap_size()) + } +} + +impl HeapSizeOf for String { + fn heap_size(&self) -> usize { + self.capacity() + } +} + +#[cfg(test)] +mod tests { + use std::{collections::BTreeMap, mem}; + + use super::ByteSize; + use types::Key; + + fn assert_byte_size(el: T, expected: usize) { + assert_eq!(el.byte_size(), expected) + } + + #[test] + fn byte_size_of_string() { + assert_byte_size("Hello".to_owned(), 5 + mem::size_of::()) + } + + #[test] + fn byte_size_of_map() { + let v = vec![ + (Key::Hash([1u8; 32]), "A".to_string()), + (Key::Hash([2u8; 32]), "B".to_string()), + (Key::Hash([3u8; 32]), "C".to_string()), + (Key::Hash([4u8; 32]), "D".to_string()), + ]; + let it_size: usize = mem::size_of::>() + + 4 * (mem::size_of::() + mem::size_of::() + 1); + let map: BTreeMap = v.into_iter().collect(); + assert_byte_size(map, it_size); + } +} diff --git a/node/src/contract_core/tracking_copy/ext.rs b/node/src/contract_core/tracking_copy/ext.rs new file mode 100644 index 0000000000..e82537ee03 --- /dev/null +++ b/node/src/contract_core/tracking_copy/ext.rs @@ -0,0 +1,243 @@ +use std::convert::TryInto; + +use crate::contract_shared::wasm_prep::{self, Preprocessor}; +use crate::contract_shared::{ + account::Account, motes::Motes, newtypes::CorrelationId, stored_value::StoredValue, wasm, + TypeMismatch, +}; +use crate::contract_storage::global_state::StateReader; +use types::{ + account::AccountHash, CLValue, Contract, ContractHash, ContractPackage, ContractPackageHash, + ContractWasm, ContractWasmHash, Key, U512, +}; + +use crate::contract_core::{execution, tracking_copy::TrackingCopy}; +use parity_wasm::elements::Module; + +pub trait TrackingCopyExt { + type Error; + + /// Gets the account at a given account address. + fn get_account( + &mut self, + correlation_id: CorrelationId, + account_hash: AccountHash, + ) -> Result; + + /// Reads the account at a given account address. + fn read_account( + &mut self, + correlation_id: CorrelationId, + account_hash: AccountHash, + ) -> Result; + + /// Gets the purse balance key for a given purse id + fn get_purse_balance_key( + &mut self, + correlation_id: CorrelationId, + purse_key: Key, + ) -> Result; + + /// Gets the balance at a given balance key + fn get_purse_balance( + &mut self, + correlation_id: CorrelationId, + balance_key: Key, + ) -> Result; + + /// Gets a contract by Key + fn get_contract_wasm( + &mut self, + correlation_id: CorrelationId, + contract_wasm_hash: ContractWasmHash, + ) -> Result; + + /// Gets a contract header by Key + fn get_contract( + &mut self, + correlation_id: CorrelationId, + contract_hash: ContractHash, + ) -> Result; + + /// Gets a contract package by Key + fn get_contract_package( + &mut self, + correlation_id: CorrelationId, + contract_package_hash: ContractPackageHash, + ) -> Result; + + fn get_system_module( + &mut self, + correlation_id: CorrelationId, + contract_wasm_hash: ContractWasmHash, + use_system_contracts: bool, + preprocessor: &Preprocessor, + ) -> Result; +} + +impl TrackingCopyExt for TrackingCopy +where + R: StateReader, + R::Error: Into, +{ + type Error = execution::Error; + + fn get_account( + &mut self, + correlation_id: CorrelationId, + account_hash: AccountHash, + ) -> Result { + let account_key = Key::Account(account_hash); + match self.get(correlation_id, &account_key).map_err(Into::into)? { + Some(StoredValue::Account(account)) => Ok(account), + Some(other) => Err(execution::Error::TypeMismatch(TypeMismatch::new( + "Account".to_string(), + other.type_name(), + ))), + None => Err(execution::Error::KeyNotFound(account_key)), + } + } + + fn read_account( + &mut self, + correlation_id: CorrelationId, + account_hash: AccountHash, + ) -> Result { + let account_key = Key::Account(account_hash); + match self + .read(correlation_id, &account_key) + .map_err(Into::into)? + { + Some(StoredValue::Account(account)) => Ok(account), + Some(other) => Err(execution::Error::TypeMismatch(TypeMismatch::new( + "Account".to_string(), + other.type_name(), + ))), + None => Err(execution::Error::KeyNotFound(account_key)), + } + } + + fn get_purse_balance_key( + &mut self, + correlation_id: CorrelationId, + purse_key: Key, + ) -> Result { + let uref = purse_key + .as_uref() + .ok_or_else(|| execution::Error::URefNotFound("public purse balance 1".to_string()))?; + let local_key_bytes = uref.addr(); + let balance_mapping_key = Key::Hash(local_key_bytes); + match self + .read(correlation_id, &balance_mapping_key) + .map_err(Into::into)? + { + Some(stored_value) => { + let cl_value: CLValue = stored_value + .try_into() + .map_err(execution::Error::TypeMismatch)?; + Ok(cl_value.into_t()?) + } + None => Err(execution::Error::URefNotFound( + "public purse balance 21".to_string(), + )), + } + } + + fn get_purse_balance( + &mut self, + correlation_id: CorrelationId, + key: Key, + ) -> Result { + let read_result = match self.read(correlation_id, &key) { + Ok(read_result) => read_result, + Err(_) => return Err(execution::Error::KeyNotFound(key)), + }; + match read_result { + Some(stored_value) => { + let cl_value: CLValue = stored_value + .try_into() + .map_err(execution::Error::TypeMismatch)?; + let balance: U512 = cl_value.into_t()?; + Ok(Motes::new(balance)) + } + None => Err(execution::Error::KeyNotFound(key)), + } + } + + /// Gets a contract wasm by Key + fn get_contract_wasm( + &mut self, + correlation_id: CorrelationId, + contract_wasm_hash: ContractWasmHash, + ) -> Result { + let key = contract_wasm_hash.into(); + match self.get(correlation_id, &key).map_err(Into::into)? { + Some(StoredValue::ContractWasm(contract_wasm)) => Ok(contract_wasm), + Some(other) => Err(execution::Error::TypeMismatch(TypeMismatch::new( + "ContractHeader".to_string(), + other.type_name(), + ))), + None => Err(execution::Error::KeyNotFound(key)), + } + } + + /// Gets a contract header by Key + fn get_contract( + &mut self, + correlation_id: CorrelationId, + contract_hash: ContractHash, + ) -> Result { + let key = contract_hash.into(); + match self.get(correlation_id, &key).map_err(Into::into)? { + Some(StoredValue::Contract(contract)) => Ok(contract), + Some(other) => Err(execution::Error::TypeMismatch(TypeMismatch::new( + "ContractHeader".to_string(), + other.type_name(), + ))), + None => Err(execution::Error::KeyNotFound(key)), + } + } + + fn get_contract_package( + &mut self, + correlation_id: CorrelationId, + contract_package_hash: ContractPackageHash, + ) -> Result { + let key = contract_package_hash.into(); + match self.get(correlation_id, &key).map_err(Into::into)? { + Some(StoredValue::ContractPackage(contract_package)) => Ok(contract_package), + Some(other) => Err(execution::Error::TypeMismatch(TypeMismatch::new( + "ContractPackage".to_string(), + other.type_name(), + ))), + None => Err(execution::Error::KeyNotFound(key)), + } + } + + fn get_system_module( + &mut self, + correlation_id: CorrelationId, + contract_wasm_hash: ContractWasmHash, + use_system_contracts: bool, + preprocessor: &Preprocessor, + ) -> Result { + match { + if use_system_contracts { + let contract_wasm = match self.get_contract_wasm(correlation_id, contract_wasm_hash) + { + Ok(contract_wasm) => contract_wasm, + Err(error) => { + return Err(error); + } + }; + + wasm_prep::deserialize(contract_wasm.bytes()) + } else { + wasm::do_nothing_module(preprocessor) + } + } { + Ok(module) => Ok(module), + Err(error) => Err(error.into()), + } + } +} diff --git a/node/src/contract_core/tracking_copy/meter.rs b/node/src/contract_core/tracking_copy/meter.rs new file mode 100644 index 0000000000..37b550c564 --- /dev/null +++ b/node/src/contract_core/tracking_copy/meter.rs @@ -0,0 +1,27 @@ +/// Trait for measuring "size" of key-value pairs. +pub trait Meter { + fn measure(&self, k: &K, v: &V) -> usize; +} + +pub mod heap_meter { + use crate::contract_core::tracking_copy::byte_size::ByteSize; + + pub struct HeapSize; + + impl super::Meter for HeapSize { + fn measure(&self, _: &K, v: &V) -> usize { + std::mem::size_of::() + v.byte_size() + } + } +} + +#[cfg(test)] +pub mod count_meter { + pub struct Count; + + impl super::Meter for Count { + fn measure(&self, _k: &K, _v: &V) -> usize { + 1 + } + } +} diff --git a/node/src/contract_core/tracking_copy/mod.rs b/node/src/contract_core/tracking_copy/mod.rs new file mode 100644 index 0000000000..7895c9b9e0 --- /dev/null +++ b/node/src/contract_core/tracking_copy/mod.rs @@ -0,0 +1,429 @@ +mod byte_size; +mod ext; +pub(self) mod meter; +#[cfg(test)] +mod tests; + +use std::{ + collections::{HashMap, HashSet, VecDeque}, + convert::From, + iter, +}; + +use linked_hash_map::LinkedHashMap; + +use crate::contract_shared::{ + additive_map::AdditiveMap, + newtypes::CorrelationId, + stored_value::StoredValue, + transform::{self, Transform}, + TypeMismatch, +}; +use crate::contract_storage::global_state::StateReader; +use types::{bytesrepr, CLType, CLValueError, Key}; + +use crate::contract_core::engine_state::{execution_effect::ExecutionEffect, op::Op}; + +pub use self::ext::TrackingCopyExt; +use self::meter::{heap_meter::HeapSize, Meter}; + +#[derive(Debug)] +pub enum TrackingCopyQueryResult { + Success(StoredValue), + ValueNotFound(String), + CircularReference(String), +} + +/// Struct containing state relating to a given query. +struct Query { + /// The key from where the search starts. + base_key: Key, + /// A collection of normalized keys which have been visited during the search. + visited_keys: HashSet, + /// The key currently being processed. + current_key: Key, + /// Path components which have not yet been followed, held in the same order in which they were + /// provided to the `query()` call. + unvisited_names: VecDeque, + /// Path components which have been followed, held in the same order in which they were + /// provided to the `query()` call. + visited_names: Vec, +} + +impl Query { + fn new(base_key: Key, path: &[String]) -> Self { + Query { + base_key, + current_key: base_key.normalize(), + unvisited_names: path.iter().cloned().collect(), + visited_names: Vec::new(), + visited_keys: HashSet::new(), + } + } + + /// Panics if `unvisited_names` is empty. + fn next_name(&mut self) -> &String { + let next_name = self.unvisited_names.pop_front().unwrap(); + self.visited_names.push(next_name); + self.visited_names.last().unwrap() + } + + fn into_not_found_result(self, msg_prefix: &str) -> TrackingCopyQueryResult { + let msg = format!("{} at path: {}", msg_prefix, self.current_path()); + TrackingCopyQueryResult::ValueNotFound(msg) + } + + fn into_circular_ref_result(self) -> TrackingCopyQueryResult { + let msg = format!( + "{:?} has formed a circular reference at path: {}", + self.current_key, + self.current_path() + ); + TrackingCopyQueryResult::CircularReference(msg) + } + + fn current_path(&self) -> String { + let mut path = format!("{:?}", self.base_key); + for name in &self.visited_names { + path.push_str("/"); + path.push_str(name); + } + path + } +} + +/// Keeps track of already accessed keys. +/// We deliberately separate cached Reads from cached mutations +/// because we want to invalidate Reads' cache so it doesn't grow too fast. +pub struct TrackingCopyCache { + max_cache_size: usize, + current_cache_size: usize, + reads_cached: LinkedHashMap, + muts_cached: HashMap, + meter: M, +} + +impl> TrackingCopyCache { + /// Creates instance of `TrackingCopyCache` with specified `max_cache_size`, + /// above which least-recently-used elements of the cache are invalidated. + /// Measurements of elements' "size" is done with the usage of `Meter` + /// instance. + pub fn new(max_cache_size: usize, meter: M) -> TrackingCopyCache { + TrackingCopyCache { + max_cache_size, + current_cache_size: 0, + reads_cached: LinkedHashMap::new(), + muts_cached: HashMap::new(), + meter, + } + } + + /// Inserts `key` and `value` pair to Read cache. + pub fn insert_read(&mut self, key: Key, value: StoredValue) { + let element_size = Meter::measure(&self.meter, &key, &value); + self.reads_cached.insert(key, value); + self.current_cache_size += element_size; + while self.current_cache_size > self.max_cache_size { + match self.reads_cached.pop_front() { + Some((k, v)) => { + let element_size = Meter::measure(&self.meter, &k, &v); + self.current_cache_size -= element_size; + } + None => break, + } + } + } + + /// Inserts `key` and `value` pair to Write/Add cache. + pub fn insert_write(&mut self, key: Key, value: StoredValue) { + self.muts_cached.insert(key, value); + } + + /// Gets value from `key` in the cache. + pub fn get(&mut self, key: &Key) -> Option<&StoredValue> { + if let Some(value) = self.muts_cached.get(&key) { + return Some(value); + }; + + self.reads_cached.get_refresh(key).map(|v| &*v) + } +} + +pub struct TrackingCopy { + reader: R, + cache: TrackingCopyCache, + ops: AdditiveMap, + fns: AdditiveMap, +} + +#[derive(Debug)] +pub enum AddResult { + Success, + KeyNotFound(Key), + TypeMismatch(TypeMismatch), + Serialization(bytesrepr::Error), +} + +impl From for AddResult { + fn from(error: CLValueError) -> Self { + match error { + CLValueError::Serialization(error) => AddResult::Serialization(error), + CLValueError::Type(type_mismatch) => { + let expected = format!("{:?}", type_mismatch.expected); + let found = format!("{:?}", type_mismatch.found); + AddResult::TypeMismatch(TypeMismatch::new(expected, found)) + } + } + } +} + +impl> TrackingCopy { + pub fn new(reader: R) -> TrackingCopy { + TrackingCopy { + reader, + cache: TrackingCopyCache::new(1024 * 16, HeapSize), + /* TODO: Should `max_cache_size` + * be fraction of wasm memory + * limit? */ + ops: AdditiveMap::new(), + fns: AdditiveMap::new(), + } + } + + pub fn reader(&self) -> &R { + &self.reader + } + + /// Creates a new TrackingCopy, using this one (including its mutations) as + /// the base state to read against. The intended use case for this + /// function is to "snapshot" the current `TrackingCopy` and produce a + /// new `TrackingCopy` where further changes can be made. This + /// allows isolating a specific set of changes (those in the new + /// `TrackingCopy`) from existing changes. Note that mutations to state + /// caused by new changes (i.e. writes and adds) only impact the new + /// `TrackingCopy`, not this one. Note that currently there is no `join` / + /// `merge` function to bring changes from a fork back to the main + /// `TrackingCopy`. this means the current usage requires repeated + /// forking, however we recognize this is sub-optimal and will revisit + /// in the future. + pub fn fork(&self) -> TrackingCopy<&TrackingCopy> { + TrackingCopy::new(self) + } + + pub fn get( + &mut self, + correlation_id: CorrelationId, + key: &Key, + ) -> Result, R::Error> { + if let Some(value) = self.cache.get(key) { + return Ok(Some(value.to_owned())); + } + if let Some(value) = self.reader.read(correlation_id, key)? { + self.cache.insert_read(*key, value.to_owned()); + Ok(Some(value)) + } else { + Ok(None) + } + } + + pub fn read( + &mut self, + correlation_id: CorrelationId, + key: &Key, + ) -> Result, R::Error> { + let normalized_key = key.normalize(); + if let Some(value) = self.get(correlation_id, &normalized_key)? { + self.ops.insert_add(normalized_key, Op::Read); + self.fns.insert_add(normalized_key, Transform::Identity); + Ok(Some(value)) + } else { + Ok(None) + } + } + + pub fn write(&mut self, key: Key, value: StoredValue) { + let normalized_key = key.normalize(); + self.cache.insert_write(normalized_key, value.clone()); + self.ops.insert_add(normalized_key, Op::Write); + self.fns.insert_add(normalized_key, Transform::Write(value)); + } + + /// Ok(None) represents missing key to which we want to "add" some value. + /// Ok(Some(unit)) represents successful operation. + /// Err(error) is reserved for unexpected errors when accessing global + /// state. + pub fn add( + &mut self, + correlation_id: CorrelationId, + key: Key, + value: StoredValue, + ) -> Result { + let normalized_key = key.normalize(); + let current_value = match self.get(correlation_id, &normalized_key)? { + None => return Ok(AddResult::KeyNotFound(normalized_key)), + Some(current_value) => current_value, + }; + + let type_name = value.type_name(); + let mismatch = || { + Ok(AddResult::TypeMismatch(TypeMismatch::new( + "I32, U64, U128, U256, U512 or (String, Key) tuple".to_string(), + type_name, + ))) + }; + + let transform = match value { + StoredValue::CLValue(cl_value) => match *cl_value.cl_type() { + CLType::I32 => match cl_value.into_t() { + Ok(value) => Transform::AddInt32(value), + Err(error) => return Ok(AddResult::from(error)), + }, + CLType::U64 => match cl_value.into_t() { + Ok(value) => Transform::AddUInt64(value), + Err(error) => return Ok(AddResult::from(error)), + }, + CLType::U128 => match cl_value.into_t() { + Ok(value) => Transform::AddUInt128(value), + Err(error) => return Ok(AddResult::from(error)), + }, + CLType::U256 => match cl_value.into_t() { + Ok(value) => Transform::AddUInt256(value), + Err(error) => return Ok(AddResult::from(error)), + }, + CLType::U512 => match cl_value.into_t() { + Ok(value) => Transform::AddUInt512(value), + Err(error) => return Ok(AddResult::from(error)), + }, + _ => { + if *cl_value.cl_type() == types::named_key_type() { + match cl_value.into_t() { + Ok(name_and_key) => { + let map = iter::once(name_and_key).collect(); + Transform::AddKeys(map) + } + Err(error) => return Ok(AddResult::from(error)), + } + } else { + return mismatch(); + } + } + }, + _ => return mismatch(), + }; + + match transform.clone().apply(current_value) { + Ok(new_value) => { + self.cache.insert_write(normalized_key, new_value); + self.ops.insert_add(normalized_key, Op::Add); + self.fns.insert_add(normalized_key, transform); + Ok(AddResult::Success) + } + Err(transform::Error::TypeMismatch(type_mismatch)) => { + Ok(AddResult::TypeMismatch(type_mismatch)) + } + Err(transform::Error::Serialization(error)) => Ok(AddResult::Serialization(error)), + } + } + + pub fn effect(&self) -> ExecutionEffect { + ExecutionEffect::new(self.ops.clone(), self.fns.clone()) + } + + /// Calling `query()` avoids calling into `self.cache`, so this will not return any values + /// written or mutated in this `TrackingCopy` via previous calls to `write()` or `add()`, since + /// these updates are only held in `self.cache`. + /// + /// The intent is that `query()` is only used to satisfy `QueryRequest`s made to the server. + /// Other EE internal use cases should call `read()` or `get()` in order to retrieve cached + /// values. + pub fn query( + &self, + correlation_id: CorrelationId, + base_key: Key, + path: &[String], + ) -> Result { + let mut query = Query::new(base_key, path); + + loop { + if !query.visited_keys.insert(query.current_key) { + return Ok(query.into_circular_ref_result()); + } + let stored_value = match self.reader.read(correlation_id, &query.current_key)? { + None => { + return Ok(query.into_not_found_result("Failed to find base key")); + } + Some(stored_value) => stored_value, + }; + + if query.unvisited_names.is_empty() { + return Ok(TrackingCopyQueryResult::Success(stored_value)); + } + + match stored_value { + StoredValue::Account(account) => { + let name = query.next_name(); + if let Some(key) = account.named_keys().get(name) { + query.current_key = key.normalize(); + } else { + let msg_prefix = format!("Name {} not found in Account", name); + return Ok(query.into_not_found_result(&msg_prefix)); + } + } + StoredValue::CLValue(cl_value) if cl_value.cl_type() == &CLType::Key => { + if let Ok(key) = cl_value.into_t::() { + query.current_key = key.normalize(); + } else { + return Ok(query.into_not_found_result("Failed to parse CLValue as Key")); + } + } + StoredValue::CLValue(cl_value) => { + let msg_prefix = format!( + "Query cannot continue as {:?} is not an account, contract nor key to \ + such. Value found", + cl_value + ); + return Ok(query.into_not_found_result(&msg_prefix)); + } + StoredValue::Contract(contract) => { + let name = query.next_name(); + if let Some(key) = contract.named_keys().get(name) { + query.current_key = key.normalize(); + } else { + let msg_prefix = format!("Name {} not found in Contract", name); + return Ok(query.into_not_found_result(&msg_prefix)); + } + } + StoredValue::ContractPackage(_) => { + return Ok(query.into_not_found_result(&"ContractPackage value found.")); + } + StoredValue::ContractWasm(_) => { + return Ok(query.into_not_found_result(&"ContractWasm value found.")); + } + } + } + } +} + +/// The purpose of this implementation is to allow a "snapshot" mechanism for +/// TrackingCopy. The state of a TrackingCopy (including the effects of +/// any transforms it has accumulated) can be read using an immutable +/// reference to that TrackingCopy via this trait implementation. See +/// `TrackingCopy::fork` for more information. +impl> StateReader for &TrackingCopy { + type Error = R::Error; + + fn read( + &self, + correlation_id: CorrelationId, + key: &Key, + ) -> Result, Self::Error> { + if let Some(value) = self.cache.muts_cached.get(key) { + return Ok(Some(value.to_owned())); + } + if let Some(value) = self.reader.read(correlation_id, key)? { + Ok(Some(value)) + } else { + Ok(None) + } + } +} diff --git a/node/src/contract_core/tracking_copy/tests.rs b/node/src/contract_core/tracking_copy/tests.rs new file mode 100644 index 0000000000..f046357091 --- /dev/null +++ b/node/src/contract_core/tracking_copy/tests.rs @@ -0,0 +1,548 @@ +use std::{cell::Cell, iter, rc::Rc}; + +use assert_matches::assert_matches; +use proptest::prelude::*; + +use crate::contract_shared::{ + account::{Account, AssociatedKeys}, + newtypes::CorrelationId, + stored_value::{gens::stored_value_arb, StoredValue}, + transform::Transform, +}; +use crate::contract_storage::global_state::{ + in_memory::InMemoryGlobalState, StateProvider, StateReader, +}; +use types::{ + account::{AccountHash, Weight, ACCOUNT_HASH_LENGTH}, + contracts::NamedKeys, + gens::*, + AccessRights, CLValue, Contract, EntryPoints, Key, ProtocolVersion, URef, +}; + +use super::{ + meter::count_meter::Count, AddResult, TrackingCopy, TrackingCopyCache, TrackingCopyQueryResult, +}; +use crate::contract_core::engine_state::op::Op; + +struct CountingDb { + count: Rc>, + value: Option, +} + +impl CountingDb { + fn new(counter: Rc>) -> CountingDb { + CountingDb { + count: counter, + value: None, + } + } + + fn new_init(v: StoredValue) -> CountingDb { + CountingDb { + count: Rc::new(Cell::new(0)), + value: Some(v), + } + } +} + +impl StateReader for CountingDb { + type Error = String; + fn read( + &self, + _correlation_id: CorrelationId, + _key: &Key, + ) -> Result, Self::Error> { + let count = self.count.get(); + let value = match self.value { + Some(ref v) => v.clone(), + None => StoredValue::CLValue(CLValue::from_t(count).unwrap()), + }; + self.count.set(count + 1); + Ok(Some(value)) + } +} + +#[test] +fn tracking_copy_new() { + let counter = Rc::new(Cell::new(0)); + let db = CountingDb::new(counter); + let tc = TrackingCopy::new(db); + + assert_eq!(tc.ops.is_empty(), true); + assert_eq!(tc.fns.is_empty(), true); +} + +#[test] +fn tracking_copy_caching() { + let correlation_id = CorrelationId::new(); + let counter = Rc::new(Cell::new(0)); + let db = CountingDb::new(Rc::clone(&counter)); + let mut tc = TrackingCopy::new(db); + let k = Key::Hash([0u8; 32]); + + let zero = StoredValue::CLValue(CLValue::from_t(0_i32).unwrap()); + // first read + let value = tc.read(correlation_id, &k).unwrap().unwrap(); + assert_eq!(value, zero); + + // second read; should use cache instead + // of going back to the DB + let value = tc.read(correlation_id, &k).unwrap().unwrap(); + let db_value = counter.get(); + assert_eq!(value, zero); + assert_eq!(db_value, 1); +} + +#[test] +fn tracking_copy_read() { + let correlation_id = CorrelationId::new(); + let counter = Rc::new(Cell::new(0)); + let db = CountingDb::new(Rc::clone(&counter)); + let mut tc = TrackingCopy::new(db); + let k = Key::Hash([0u8; 32]); + + let zero = StoredValue::CLValue(CLValue::from_t(0_i32).unwrap()); + let value = tc.read(correlation_id, &k).unwrap().unwrap(); + // value read correctly + assert_eq!(value, zero); + // read produces an identity transform + assert_eq!(tc.fns.len(), 1); + assert_eq!(tc.fns.get(&k), Some(&Transform::Identity)); + // read does produce an op + assert_eq!(tc.ops.len(), 1); + assert_eq!(tc.ops.get(&k), Some(&Op::Read)); +} + +#[test] +fn tracking_copy_write() { + let counter = Rc::new(Cell::new(0)); + let db = CountingDb::new(Rc::clone(&counter)); + let mut tc = TrackingCopy::new(db); + let k = Key::Hash([0u8; 32]); + + let one = StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()); + let two = StoredValue::CLValue(CLValue::from_t(2_i32).unwrap()); + + // writing should work + tc.write(k, one.clone()); + // write does not need to query the DB + let db_value = counter.get(); + assert_eq!(db_value, 0); + // write creates a Transfrom + assert_eq!(tc.fns.len(), 1); + assert_eq!(tc.fns.get(&k), Some(&Transform::Write(one))); + // write creates an Op + assert_eq!(tc.ops.len(), 1); + assert_eq!(tc.ops.get(&k), Some(&Op::Write)); + + // writing again should update the values + tc.write(k, two.clone()); + let db_value = counter.get(); + assert_eq!(db_value, 0); + assert_eq!(tc.fns.len(), 1); + assert_eq!(tc.fns.get(&k), Some(&Transform::Write(two))); + assert_eq!(tc.ops.len(), 1); + assert_eq!(tc.ops.get(&k), Some(&Op::Write)); +} + +#[test] +fn tracking_copy_add_i32() { + let correlation_id = CorrelationId::new(); + let counter = Rc::new(Cell::new(0)); + let db = CountingDb::new(counter); + let mut tc = TrackingCopy::new(db); + let k = Key::Hash([0u8; 32]); + + let three = StoredValue::CLValue(CLValue::from_t(3_i32).unwrap()); + + // adding should work + let add = tc.add(correlation_id, k, three.clone()); + assert_matches!(add, Ok(_)); + + // add creates a Transfrom + assert_eq!(tc.fns.len(), 1); + assert_eq!(tc.fns.get(&k), Some(&Transform::AddInt32(3))); + // add creates an Op + assert_eq!(tc.ops.len(), 1); + assert_eq!(tc.ops.get(&k), Some(&Op::Add)); + + // adding again should update the values + let add = tc.add(correlation_id, k, three); + assert_matches!(add, Ok(_)); + assert_eq!(tc.fns.len(), 1); + assert_eq!(tc.fns.get(&k), Some(&Transform::AddInt32(6))); + assert_eq!(tc.ops.len(), 1); + assert_eq!(tc.ops.get(&k), Some(&Op::Add)); +} + +#[test] +fn tracking_copy_add_named_key() { + let zero_account_hash = AccountHash::new([0u8; ACCOUNT_HASH_LENGTH]); + let correlation_id = CorrelationId::new(); + // DB now holds an `Account` so that we can test adding a `NamedKey` + let associated_keys = AssociatedKeys::new(zero_account_hash, Weight::new(1)); + let account = Account::new( + zero_account_hash, + NamedKeys::new(), + URef::new([0u8; 32], AccessRights::READ_ADD_WRITE), + associated_keys, + Default::default(), + ); + let db = CountingDb::new_init(StoredValue::Account(account)); + let mut tc = TrackingCopy::new(db); + let k = Key::Hash([0u8; 32]); + let u1 = Key::URef(URef::new([1u8; 32], AccessRights::READ_WRITE)); + let u2 = Key::URef(URef::new([2u8; 32], AccessRights::READ_WRITE)); + + let name1 = "test".to_string(); + let named_key = StoredValue::CLValue(CLValue::from_t((name1.clone(), u1)).unwrap()); + let name2 = "test2".to_string(); + let other_named_key = StoredValue::CLValue(CLValue::from_t((name2.clone(), u2)).unwrap()); + let mut map = NamedKeys::new(); + map.insert(name1, u1); + + // adding the wrong type should fail + let failed_add = tc.add( + correlation_id, + k, + StoredValue::CLValue(CLValue::from_t(3_i32).unwrap()), + ); + assert_matches!(failed_add, Ok(AddResult::TypeMismatch(_))); + assert_eq!(tc.ops.is_empty(), true); + assert_eq!(tc.fns.is_empty(), true); + + // adding correct type works + let add = tc.add(correlation_id, k, named_key); + assert_matches!(add, Ok(_)); + // add creates a Transfrom + assert_eq!(tc.fns.len(), 1); + assert_eq!(tc.fns.get(&k), Some(&Transform::AddKeys(map.clone()))); + // add creates an Op + assert_eq!(tc.ops.len(), 1); + assert_eq!(tc.ops.get(&k), Some(&Op::Add)); + + // adding again updates the values + map.insert(name2, u2); + let add = tc.add(correlation_id, k, other_named_key); + assert_matches!(add, Ok(_)); + assert_eq!(tc.fns.len(), 1); + assert_eq!(tc.fns.get(&k), Some(&Transform::AddKeys(map))); + assert_eq!(tc.ops.len(), 1); + assert_eq!(tc.ops.get(&k), Some(&Op::Add)); +} + +#[test] +fn tracking_copy_rw() { + let correlation_id = CorrelationId::new(); + let counter = Rc::new(Cell::new(0)); + let db = CountingDb::new(counter); + let mut tc = TrackingCopy::new(db); + let k = Key::Hash([0u8; 32]); + + // reading then writing should update the op + let value = StoredValue::CLValue(CLValue::from_t(3_i32).unwrap()); + let _ = tc.read(correlation_id, &k); + tc.write(k, value.clone()); + assert_eq!(tc.fns.len(), 1); + assert_eq!(tc.fns.get(&k), Some(&Transform::Write(value))); + assert_eq!(tc.ops.len(), 1); + assert_eq!(tc.ops.get(&k), Some(&Op::Write)); +} + +#[test] +fn tracking_copy_ra() { + let correlation_id = CorrelationId::new(); + let counter = Rc::new(Cell::new(0)); + let db = CountingDb::new(counter); + let mut tc = TrackingCopy::new(db); + let k = Key::Hash([0u8; 32]); + + // reading then adding should update the op + let value = StoredValue::CLValue(CLValue::from_t(3_i32).unwrap()); + let _ = tc.read(correlation_id, &k); + let _ = tc.add(correlation_id, k, value); + assert_eq!(tc.fns.len(), 1); + assert_eq!(tc.fns.get(&k), Some(&Transform::AddInt32(3))); + assert_eq!(tc.ops.len(), 1); + // this Op is correct because Read+Add = Write + assert_eq!(tc.ops.get(&k), Some(&Op::Write)); +} + +#[test] +fn tracking_copy_aw() { + let correlation_id = CorrelationId::new(); + let counter = Rc::new(Cell::new(0)); + let db = CountingDb::new(counter); + let mut tc = TrackingCopy::new(db); + let k = Key::Hash([0u8; 32]); + + // adding then writing should update the op + let value = StoredValue::CLValue(CLValue::from_t(3_i32).unwrap()); + let write_value = StoredValue::CLValue(CLValue::from_t(7_i32).unwrap()); + let _ = tc.add(correlation_id, k, value); + tc.write(k, write_value.clone()); + assert_eq!(tc.fns.len(), 1); + assert_eq!(tc.fns.get(&k), Some(&Transform::Write(write_value))); + assert_eq!(tc.ops.len(), 1); + assert_eq!(tc.ops.get(&k), Some(&Op::Write)); +} + +proptest! { + #[test] + fn query_empty_path(k in key_arb(), missing_key in key_arb(), v in stored_value_arb()) { + let correlation_id = CorrelationId::new(); + let (gs, root_hash) = InMemoryGlobalState::from_pairs(correlation_id, &[(k, v.to_owned())]).unwrap(); + let view = gs.checkout(root_hash).unwrap().unwrap(); + let tc = TrackingCopy::new(view); + let empty_path = Vec::new(); + if let Ok(TrackingCopyQueryResult::Success(result)) = tc.query(correlation_id, k, &empty_path) { + assert_eq!(v, result); + } else { + panic!("Query failed when it should not have!"); + } + + if missing_key != k { + let result = tc.query(correlation_id, missing_key, &empty_path); + assert_matches!(result, Ok(TrackingCopyQueryResult::ValueNotFound(_))); + } + } + + #[test] + fn query_contract_state( + k in key_arb(), // key state is stored at + v in stored_value_arb(), // value in contract state + name in "\\PC*", // human-readable name for state + missing_name in "\\PC*", + hash in u8_slice_32(), // hash for contract key + ) { + let correlation_id = CorrelationId::new(); + let mut named_keys = NamedKeys::new(); + named_keys.insert(name.clone(), k); + let contract = + StoredValue::Contract(Contract::new( + [2; 32], + [3; 32], + named_keys, + EntryPoints::default(), + ProtocolVersion::V1_0_0, + )); + let contract_key = Key::Hash(hash); + + let (gs, root_hash) = InMemoryGlobalState::from_pairs( + correlation_id, + &[(k, v.to_owned()), (contract_key, contract)] + ).unwrap(); + let view = gs.checkout(root_hash).unwrap().unwrap(); + let tc = TrackingCopy::new(view); + let path = vec!(name.clone()); + if let Ok(TrackingCopyQueryResult::Success(result)) = tc.query(correlation_id, contract_key, &path) { + assert_eq!(v, result); + } else { + panic!("Query failed when it should not have!"); + } + + if missing_name != name { + let result = tc.query(correlation_id, contract_key, &[missing_name]); + assert_matches!(result, Ok(TrackingCopyQueryResult::ValueNotFound(_))); + } + } + + + #[test] + fn query_account_state( + k in key_arb(), // key state is stored at + v in stored_value_arb(), // value in account state + name in "\\PC*", // human-readable name for state + missing_name in "\\PC*", + pk in account_hash_arb(), // account hash + address in account_hash_arb(), // address for account hash + ) { + let correlation_id = CorrelationId::new(); + let named_keys = iter::once((name.clone(), k)).collect(); + let purse = URef::new([0u8; 32], AccessRights::READ_ADD_WRITE); + let associated_keys = AssociatedKeys::new(pk, Weight::new(1)); + let account = Account::new( + pk, + named_keys, + purse, + associated_keys, + Default::default(), + ); + let account_key = Key::Account(address); + + let (gs, root_hash) = InMemoryGlobalState::from_pairs( + correlation_id, + &[(k, v.to_owned()), (account_key, StoredValue::Account(account))], + ).unwrap(); + let view = gs.checkout(root_hash).unwrap().unwrap(); + let tc = TrackingCopy::new(view); + let path = vec!(name.clone()); + if let Ok(TrackingCopyQueryResult::Success(result)) = tc.query(correlation_id, account_key, &path) { + assert_eq!(v, result); + } else { + panic!("Query failed when it should not have!"); + } + + if missing_name != name { + let result = tc.query(correlation_id, account_key, &[missing_name]); + assert_matches!(result, Ok(TrackingCopyQueryResult::ValueNotFound(_))); + } + } + + #[test] + fn query_path( + k in key_arb(), // key state is stored at + v in stored_value_arb(), // value in contract state + state_name in "\\PC*", // human-readable name for state + contract_name in "\\PC*", // human-readable name for contract + pk in account_hash_arb(), // account hash + address in account_hash_arb(), // address for account hash + hash in u8_slice_32(), // hash for contract key + ) { + let correlation_id = CorrelationId::new(); + // create contract which knows about value + let mut contract_named_keys = NamedKeys::new(); + contract_named_keys.insert(state_name.clone(), k); + let contract = + StoredValue::Contract(Contract::new( + [2; 32], + [3; 32], + contract_named_keys, + EntryPoints::default(), + ProtocolVersion::V1_0_0, + )); + let contract_key = Key::Hash(hash); + + // create account which knows about contract + let mut account_named_keys = NamedKeys::new(); + account_named_keys.insert(contract_name.clone(), contract_key); + let purse = URef::new([0u8; 32], AccessRights::READ_ADD_WRITE); + let associated_keys = AssociatedKeys::new(pk, Weight::new(1)); + let account = Account::new( + pk, + account_named_keys, + purse, + associated_keys, + Default::default(), + ); + let account_key = Key::Account(address); + + let (gs, root_hash) = InMemoryGlobalState::from_pairs(correlation_id, &[ + (k, v.to_owned()), + (contract_key, contract), + (account_key, StoredValue::Account(account)), + ]).unwrap(); + let view = gs.checkout(root_hash).unwrap().unwrap(); + let tc = TrackingCopy::new(view); + let path = vec!(contract_name, state_name); + + let result = tc.query(correlation_id, account_key, &path); + if let Ok(TrackingCopyQueryResult::Success(result)) = result { + assert_eq!(v, result); + } else { + panic!("Query failed when it should not have!"); + } + } +} + +#[test] +fn cache_reads_invalidation() { + let mut tc_cache = TrackingCopyCache::new(2, Count); + let (k1, v1) = ( + Key::Hash([1u8; 32]), + StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()), + ); + let (k2, v2) = ( + Key::Hash([2u8; 32]), + StoredValue::CLValue(CLValue::from_t(2_i32).unwrap()), + ); + let (k3, v3) = ( + Key::Hash([3u8; 32]), + StoredValue::CLValue(CLValue::from_t(3_i32).unwrap()), + ); + tc_cache.insert_read(k1, v1); + tc_cache.insert_read(k2, v2.clone()); + tc_cache.insert_read(k3, v3.clone()); + assert!(tc_cache.get(&k1).is_none()); // first entry should be invalidated + assert_eq!(tc_cache.get(&k2), Some(&v2)); // k2 and k3 should be there + assert_eq!(tc_cache.get(&k3), Some(&v3)); +} + +#[test] +fn cache_writes_not_invalidated() { + let mut tc_cache = TrackingCopyCache::new(2, Count); + let (k1, v1) = ( + Key::Hash([1u8; 32]), + StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()), + ); + let (k2, v2) = ( + Key::Hash([2u8; 32]), + StoredValue::CLValue(CLValue::from_t(2_i32).unwrap()), + ); + let (k3, v3) = ( + Key::Hash([3u8; 32]), + StoredValue::CLValue(CLValue::from_t(3_i32).unwrap()), + ); + tc_cache.insert_write(k1, v1.clone()); + tc_cache.insert_read(k2, v2.clone()); + tc_cache.insert_read(k3, v3.clone()); + // Writes are not subject to cache invalidation + assert_eq!(tc_cache.get(&k1), Some(&v1)); + assert_eq!(tc_cache.get(&k2), Some(&v2)); // k2 and k3 should be there + assert_eq!(tc_cache.get(&k3), Some(&v3)); +} + +#[test] +fn query_for_circular_references_should_fail() { + // create self-referential key + let cl_value_key = Key::URef(URef::new([255; 32], AccessRights::READ)); + let cl_value = StoredValue::CLValue(CLValue::from_t(cl_value_key).unwrap()); + let key_name = "key".to_string(); + + // create contract with this self-referential key in its named keys, and also a key referring to + // itself in its named keys. + let contract_key = Key::Hash([1; 32]); + let contract_name = "contract".to_string(); + let mut named_keys = NamedKeys::new(); + named_keys.insert(key_name.clone(), cl_value_key); + named_keys.insert(contract_name.clone(), contract_key); + let contract = StoredValue::Contract(Contract::new( + [2; 32], + [3; 32], + named_keys, + EntryPoints::default(), + ProtocolVersion::V1_0_0, + )); + + let correlation_id = CorrelationId::new(); + let (global_state, root_hash) = InMemoryGlobalState::from_pairs( + correlation_id, + &[(cl_value_key, cl_value), (contract_key, contract)], + ) + .unwrap(); + let view = global_state.checkout(root_hash).unwrap().unwrap(); + let tracking_copy = TrackingCopy::new(view); + + // query for the self-referential key (second path element of arbitrary value required to cause + // iteration _into_ the self-referential key) + let path = vec![key_name, String::new()]; + if let Ok(TrackingCopyQueryResult::CircularReference(msg)) = + tracking_copy.query(correlation_id, contract_key, &path) + { + let expected_path_msg = format!("at path: {:?}/{}", contract_key, path[0]); + assert!(msg.contains(&expected_path_msg)); + } else { + panic!("Query didn't fail with a circular reference error"); + } + + // query for itself in its own named keys + let path = vec![contract_name]; + if let Ok(TrackingCopyQueryResult::CircularReference(msg)) = + tracking_copy.query(correlation_id, contract_key, &path) + { + let expected_path_msg = format!("at path: {:?}/{}", contract_key, path[0]); + assert!(msg.contains(&expected_path_msg)); + } else { + panic!("Query didn't fail with a circular reference error"); + } +} diff --git a/node/src/contract_shared.rs b/node/src/contract_shared.rs new file mode 100644 index 0000000000..2fab762ed8 --- /dev/null +++ b/node/src/contract_shared.rs @@ -0,0 +1,21 @@ +#![allow(missing_docs)] + +pub mod additive_map; +#[macro_use] +pub mod gas; +pub mod account; +pub mod logging; +pub mod motes; +pub mod newtypes; +pub mod page_size; +pub mod socket; +pub mod stored_value; +pub mod test_utils; +pub mod transform; +mod type_mismatch; +pub mod utils; +pub mod wasm; +pub mod wasm_costs; +pub mod wasm_prep; + +pub use type_mismatch::TypeMismatch; diff --git a/node/src/contract_shared/account.rs b/node/src/contract_shared/account.rs new file mode 100644 index 0000000000..ae3bb113df --- /dev/null +++ b/node/src/contract_shared/account.rs @@ -0,0 +1,655 @@ +mod action_thresholds; +mod associated_keys; + +use std::collections::BTreeSet; + +use types::{ + account::{ + AccountHash, ActionType, AddKeyFailure, RemoveKeyFailure, SetThresholdFailure, + UpdateKeyFailure, Weight, + }, + bytesrepr::{self, Error, FromBytes, ToBytes}, + contracts::NamedKeys, + AccessRights, URef, +}; + +pub use action_thresholds::ActionThresholds; +pub use associated_keys::AssociatedKeys; + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct Account { + account_hash: AccountHash, + named_keys: NamedKeys, + main_purse: URef, + associated_keys: AssociatedKeys, + action_thresholds: ActionThresholds, +} + +impl Account { + pub fn new( + account_hash: AccountHash, + named_keys: NamedKeys, + main_purse: URef, + associated_keys: AssociatedKeys, + action_thresholds: ActionThresholds, + ) -> Self { + Account { + account_hash, + named_keys, + main_purse, + associated_keys, + action_thresholds, + } + } + + pub fn create(account: AccountHash, named_keys: NamedKeys, main_purse: URef) -> Self { + let associated_keys = AssociatedKeys::new(account, Weight::new(1)); + let action_thresholds: ActionThresholds = Default::default(); + Account::new( + account, + named_keys, + main_purse, + associated_keys, + action_thresholds, + ) + } + + pub fn named_keys_append(&mut self, keys: &mut NamedKeys) { + self.named_keys.append(keys); + } + + pub fn named_keys(&self) -> &NamedKeys { + &self.named_keys + } + + pub fn named_keys_mut(&mut self) -> &mut NamedKeys { + &mut self.named_keys + } + + pub fn account_hash(&self) -> AccountHash { + self.account_hash + } + + pub fn main_purse(&self) -> URef { + self.main_purse + } + + /// Returns an [`AccessRights::ADD`]-only version of the [`URef`]. + pub fn main_purse_add_only(&self) -> URef { + URef::new(self.main_purse.addr(), AccessRights::ADD) + } + + pub fn get_associated_keys(&self) -> impl Iterator { + self.associated_keys.iter() + } + + pub fn action_thresholds(&self) -> &ActionThresholds { + &self.action_thresholds + } + + pub fn add_associated_key( + &mut self, + account_hash: AccountHash, + weight: Weight, + ) -> Result<(), AddKeyFailure> { + self.associated_keys.add_key(account_hash, weight) + } + + /// Checks if removing given key would properly satisfy thresholds. + fn can_remove_key(&self, account_hash: AccountHash) -> bool { + let total_weight_without = self + .associated_keys + .total_keys_weight_excluding(account_hash); + + // Returns true if the total weight calculated without given public key would be greater or + // equal to all of the thresholds. + total_weight_without >= *self.action_thresholds().deployment() + && total_weight_without >= *self.action_thresholds().key_management() + } + + /// Checks if adding a weight to a sum of all weights excluding the given key would make the + /// resulting value to fall below any of the thresholds on account. + fn can_update_key(&self, account_hash: AccountHash, weight: Weight) -> bool { + // Calculates total weight of all keys excluding the given key + let total_weight = self + .associated_keys + .total_keys_weight_excluding(account_hash); + + // Safely calculate new weight by adding the updated weight + let new_weight = total_weight.value().saturating_add(weight.value()); + + // Returns true if the new weight would be greater or equal to all of + // the thresholds. + new_weight >= self.action_thresholds().deployment().value() + && new_weight >= self.action_thresholds().key_management().value() + } + + pub fn remove_associated_key( + &mut self, + account_hash: AccountHash, + ) -> Result<(), RemoveKeyFailure> { + if self.associated_keys.contains_key(&account_hash) { + // Check if removing this weight would fall below thresholds + if !self.can_remove_key(account_hash) { + return Err(RemoveKeyFailure::ThresholdViolation); + } + } + self.associated_keys.remove_key(&account_hash) + } + + pub fn update_associated_key( + &mut self, + account_hash: AccountHash, + weight: Weight, + ) -> Result<(), UpdateKeyFailure> { + if let Some(current_weight) = self.associated_keys.get(&account_hash) { + if weight < *current_weight { + // New weight is smaller than current weight + if !self.can_update_key(account_hash, weight) { + return Err(UpdateKeyFailure::ThresholdViolation); + } + } + } + self.associated_keys.update_key(account_hash, weight) + } + + pub fn get_associated_key_weight(&self, account_hash: AccountHash) -> Option<&Weight> { + self.associated_keys.get(&account_hash) + } + + pub fn set_action_threshold( + &mut self, + action_type: ActionType, + weight: Weight, + ) -> Result<(), SetThresholdFailure> { + // Verify if new threshold weight exceeds total weight of allassociated + // keys. + self.can_set_threshold(weight)?; + // Set new weight for given action + self.action_thresholds.set_threshold(action_type, weight) + } + + /// Verifies if user can set action threshold + pub fn can_set_threshold(&self, new_threshold: Weight) -> Result<(), SetThresholdFailure> { + let total_weight = self.associated_keys.total_keys_weight(); + if new_threshold > total_weight { + return Err(SetThresholdFailure::InsufficientTotalWeight); + } + Ok(()) + } + + /// Checks whether all authorization keys are associated with this account + pub fn can_authorize(&self, authorization_keys: &BTreeSet) -> bool { + !authorization_keys.is_empty() + && authorization_keys + .iter() + .all(|e| self.associated_keys.contains_key(e)) + } + + /// Checks whether the sum of the weights of all authorization keys is + /// greater or equal to deploy threshold. + pub fn can_deploy_with(&self, authorization_keys: &BTreeSet) -> bool { + let total_weight = self + .associated_keys + .calculate_keys_weight(authorization_keys); + + total_weight >= *self.action_thresholds().deployment() + } + + /// Checks whether the sum of the weights of all authorization keys is + /// greater or equal to key management threshold. + pub fn can_manage_keys_with(&self, authorization_keys: &BTreeSet) -> bool { + let total_weight = self + .associated_keys + .calculate_keys_weight(authorization_keys); + + total_weight >= *self.action_thresholds().key_management() + } +} + +impl ToBytes for Account { + fn to_bytes(&self) -> Result, Error> { + let mut result = bytesrepr::allocate_buffer(self)?; + result.append(&mut self.account_hash.to_bytes()?); + result.append(&mut self.named_keys.to_bytes()?); + result.append(&mut self.main_purse.to_bytes()?); + result.append(&mut self.associated_keys.to_bytes()?); + result.append(&mut self.action_thresholds.to_bytes()?); + Ok(result) + } + + fn serialized_length(&self) -> usize { + self.account_hash.serialized_length() + + self.named_keys.serialized_length() + + self.main_purse.serialized_length() + + self.associated_keys.serialized_length() + + self.action_thresholds.serialized_length() + } +} + +impl FromBytes for Account { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (account_hash, rem) = AccountHash::from_bytes(bytes)?; + let (named_keys, rem) = NamedKeys::from_bytes(rem)?; + let (main_purse, rem) = URef::from_bytes(rem)?; + let (associated_keys, rem) = AssociatedKeys::from_bytes(rem)?; + let (action_thresholds, rem) = ActionThresholds::from_bytes(rem)?; + Ok(( + Account { + account_hash, + named_keys, + main_purse, + associated_keys, + action_thresholds, + }, + rem, + )) + } +} + +#[cfg(any(feature = "gens", test))] +pub mod gens { + use proptest::prelude::*; + + use types::{ + account::MAX_ASSOCIATED_KEYS, + gens::{account_hash_arb, named_keys_arb, uref_arb}, + }; + + use super::*; + use crate::contract_shared::account::{ + action_thresholds::gens::action_thresholds_arb, associated_keys::gens::associated_keys_arb, + }; + + prop_compose! { + pub fn account_arb()( + account_hash in account_hash_arb(), + urefs in named_keys_arb(3), + purse in uref_arb(), + thresholds in action_thresholds_arb(), + mut associated_keys in associated_keys_arb(MAX_ASSOCIATED_KEYS - 1), + ) -> Account { + associated_keys.add_key(account_hash, Weight::new(1)).unwrap(); + Account::new( + account_hash, + urefs, + purse, + associated_keys, + thresholds, + ) + } + } +} + +#[cfg(test)] +mod proptests { + use proptest::prelude::*; + + use types::bytesrepr; + + use super::*; + + proptest! { + #[test] + fn test_value_account(acct in gens::account_arb()) { + bytesrepr::test_serialization_roundtrip(&acct); + } + } +} + +#[cfg(test)] +mod tests { + use std::{collections::BTreeSet, iter::FromIterator}; + + use types::{ + account::{ + AccountHash, ActionType, RemoveKeyFailure, SetThresholdFailure, UpdateKeyFailure, + Weight, + }, + AccessRights, URef, + }; + + use super::*; + + #[test] + fn associated_keys_can_authorize_keys() { + let key_1 = AccountHash::new([0; 32]); + let key_2 = AccountHash::new([1; 32]); + let key_3 = AccountHash::new([2; 32]); + let mut keys = AssociatedKeys::default(); + + keys.add_key(key_2, Weight::new(2)) + .expect("should add key_1"); + keys.add_key(key_1, Weight::new(1)) + .expect("should add key_1"); + keys.add_key(key_3, Weight::new(3)) + .expect("should add key_1"); + + let account = Account::new( + AccountHash::new([0u8; 32]), + NamedKeys::new(), + URef::new([0u8; 32], AccessRights::READ_ADD_WRITE), + keys, + // deploy: 33 (3*11) + ActionThresholds::new(Weight::new(33), Weight::new(48)) + .expect("should create thresholds"), + ); + + assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_3, key_2, key_1]))); + assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_1, key_3, key_2]))); + + assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_1, key_2]))); + assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_1]))); + + assert!(!account.can_authorize(&BTreeSet::from_iter(vec![ + key_1, + key_2, + AccountHash::new([42; 32]) + ]))); + assert!(!account.can_authorize(&BTreeSet::from_iter(vec![ + AccountHash::new([42; 32]), + key_1, + key_2 + ]))); + assert!(!account.can_authorize(&BTreeSet::from_iter(vec![ + AccountHash::new([43; 32]), + AccountHash::new([44; 32]), + AccountHash::new([42; 32]) + ]))); + assert!(!account.can_authorize(&BTreeSet::new())); + } + + #[test] + fn account_can_deploy_with() { + let associated_keys = { + let mut res = AssociatedKeys::new(AccountHash::new([1u8; 32]), Weight::new(1)); + res.add_key(AccountHash::new([2u8; 32]), Weight::new(11)) + .expect("should add key 1"); + res.add_key(AccountHash::new([3u8; 32]), Weight::new(11)) + .expect("should add key 2"); + res.add_key(AccountHash::new([4u8; 32]), Weight::new(11)) + .expect("should add key 3"); + res + }; + let account = Account::new( + AccountHash::new([0u8; 32]), + NamedKeys::new(), + URef::new([0u8; 32], AccessRights::READ_ADD_WRITE), + associated_keys, + // deploy: 33 (3*11) + ActionThresholds::new(Weight::new(33), Weight::new(48)) + .expect("should create thresholds"), + ); + + // sum: 22, required 33 - can't deploy + assert!(!account.can_deploy_with(&BTreeSet::from_iter(vec![ + AccountHash::new([3u8; 32]), + AccountHash::new([2u8; 32]), + ]))); + + // sum: 33, required 33 - can deploy + assert!(account.can_deploy_with(&BTreeSet::from_iter(vec![ + AccountHash::new([4u8; 32]), + AccountHash::new([3u8; 32]), + AccountHash::new([2u8; 32]), + ]))); + + // sum: 34, required 33 - can deploy + assert!(account.can_deploy_with(&BTreeSet::from_iter(vec![ + AccountHash::new([2u8; 32]), + AccountHash::new([1u8; 32]), + AccountHash::new([4u8; 32]), + AccountHash::new([3u8; 32]), + ]))); + } + + #[test] + fn account_can_manage_keys_with() { + let associated_keys = { + let mut res = AssociatedKeys::new(AccountHash::new([1u8; 32]), Weight::new(1)); + res.add_key(AccountHash::new([2u8; 32]), Weight::new(11)) + .expect("should add key 1"); + res.add_key(AccountHash::new([3u8; 32]), Weight::new(11)) + .expect("should add key 2"); + res.add_key(AccountHash::new([4u8; 32]), Weight::new(11)) + .expect("should add key 3"); + res + }; + let account = Account::new( + AccountHash::new([0u8; 32]), + NamedKeys::new(), + URef::new([0u8; 32], AccessRights::READ_ADD_WRITE), + associated_keys, + // deploy: 33 (3*11) + ActionThresholds::new(Weight::new(11), Weight::new(33)) + .expect("should create thresholds"), + ); + + // sum: 22, required 33 - can't manage + assert!(!account.can_manage_keys_with(&BTreeSet::from_iter(vec![ + AccountHash::new([3u8; 32]), + AccountHash::new([2u8; 32]), + ]))); + + // sum: 33, required 33 - can manage + assert!(account.can_manage_keys_with(&BTreeSet::from_iter(vec![ + AccountHash::new([4u8; 32]), + AccountHash::new([3u8; 32]), + AccountHash::new([2u8; 32]), + ]))); + + // sum: 34, required 33 - can manage + assert!(account.can_manage_keys_with(&BTreeSet::from_iter(vec![ + AccountHash::new([2u8; 32]), + AccountHash::new([1u8; 32]), + AccountHash::new([4u8; 32]), + AccountHash::new([3u8; 32]), + ]))); + } + + #[test] + fn set_action_threshold_higher_than_total_weight() { + let identity_key = AccountHash::new([1u8; 32]); + let key_1 = AccountHash::new([2u8; 32]); + let key_2 = AccountHash::new([3u8; 32]); + let key_3 = AccountHash::new([4u8; 32]); + let associated_keys = { + let mut res = AssociatedKeys::new(identity_key, Weight::new(1)); + res.add_key(key_1, Weight::new(2)) + .expect("should add key 1"); + res.add_key(key_2, Weight::new(3)) + .expect("should add key 2"); + res.add_key(key_3, Weight::new(4)) + .expect("should add key 3"); + res + }; + let mut account = Account::new( + AccountHash::new([0u8; 32]), + NamedKeys::new(), + URef::new([0u8; 32], AccessRights::READ_ADD_WRITE), + associated_keys, + // deploy: 33 (3*11) + ActionThresholds::new(Weight::new(33), Weight::new(48)) + .expect("should create thresholds"), + ); + + assert_eq!( + account + .set_action_threshold(ActionType::Deployment, Weight::new(1 + 2 + 3 + 4 + 1)) + .unwrap_err(), + SetThresholdFailure::InsufficientTotalWeight, + ); + assert_eq!( + account + .set_action_threshold(ActionType::Deployment, Weight::new(1 + 2 + 3 + 4 + 245)) + .unwrap_err(), + SetThresholdFailure::InsufficientTotalWeight, + ) + } + + #[test] + fn remove_key_would_violate_action_thresholds() { + let identity_key = AccountHash::new([1u8; 32]); + let key_1 = AccountHash::new([2u8; 32]); + let key_2 = AccountHash::new([3u8; 32]); + let key_3 = AccountHash::new([4u8; 32]); + let associated_keys = { + let mut res = AssociatedKeys::new(identity_key, Weight::new(1)); + res.add_key(key_1, Weight::new(2)) + .expect("should add key 1"); + res.add_key(key_2, Weight::new(3)) + .expect("should add key 2"); + res.add_key(key_3, Weight::new(4)) + .expect("should add key 3"); + res + }; + let mut account = Account::new( + AccountHash::new([0u8; 32]), + NamedKeys::new(), + URef::new([0u8; 32], AccessRights::READ_ADD_WRITE), + associated_keys, + // deploy: 33 (3*11) + ActionThresholds::new(Weight::new(1 + 2 + 3 + 4), Weight::new(1 + 2 + 3 + 4 + 5)) + .expect("should create thresholds"), + ); + + assert_eq!( + account.remove_associated_key(key_3).unwrap_err(), + RemoveKeyFailure::ThresholdViolation, + ) + } + + #[test] + fn updating_key_would_violate_action_thresholds() { + let identity_key = AccountHash::new([1u8; 32]); + let identity_key_weight = Weight::new(1); + let key_1 = AccountHash::new([2u8; 32]); + let key_1_weight = Weight::new(2); + let key_2 = AccountHash::new([3u8; 32]); + let key_2_weight = Weight::new(3); + let key_3 = AccountHash::new([4u8; 32]); + let key_3_weight = Weight::new(4); + let associated_keys = { + let mut res = AssociatedKeys::new(identity_key, identity_key_weight); + res.add_key(key_1, key_1_weight).expect("should add key 1"); + res.add_key(key_2, key_2_weight).expect("should add key 2"); + res.add_key(key_3, key_3_weight).expect("should add key 3"); + // 1 + 2 + 3 + 4 + res + }; + + let deployment_threshold = Weight::new( + identity_key_weight.value() + + key_1_weight.value() + + key_2_weight.value() + + key_3_weight.value(), + ); + let key_management_threshold = Weight::new(deployment_threshold.value() + 1); + let mut account = Account::new( + identity_key, + NamedKeys::new(), + URef::new([0u8; 32], AccessRights::READ_ADD_WRITE), + associated_keys, + // deploy: 33 (3*11) + ActionThresholds::new(deployment_threshold, key_management_threshold) + .expect("should create thresholds"), + ); + + // Decreases by 3 + assert_eq!( + account + .clone() + .update_associated_key(key_3, Weight::new(1)) + .unwrap_err(), + UpdateKeyFailure::ThresholdViolation, + ); + + // increase total weight (12) + account + .update_associated_key(identity_key, Weight::new(3)) + .unwrap(); + + // variant a) decrease total weight by 1 (total 11) + account + .clone() + .update_associated_key(key_3, Weight::new(3)) + .unwrap(); + // variant b) decrease total weight by 3 (total 9) - fail + assert_eq!( + account + .update_associated_key(key_3, Weight::new(1)) + .unwrap_err(), + UpdateKeyFailure::ThresholdViolation + ); + } + + #[test] + fn overflowing_should_allow_removal() { + let identity_key = AccountHash::new([42; 32]); + let key_1 = AccountHash::new([2u8; 32]); + let key_2 = AccountHash::new([3u8; 32]); + + let associated_keys = { + // Identity + let mut res = AssociatedKeys::new(identity_key, Weight::new(1)); + + // Spare key + res.add_key(key_1, Weight::new(2)) + .expect("should add key 1"); + // Big key + res.add_key(key_2, Weight::new(255)) + .expect("should add key 2"); + + res + }; + + let mut account = Account::new( + identity_key, + NamedKeys::new(), + URef::new([0u8; 32], AccessRights::READ_ADD_WRITE), + associated_keys, + ActionThresholds::new(Weight::new(1), Weight::new(254)) + .expect("should create thresholds"), + ); + + account.remove_associated_key(key_1).expect("should work") + } + + #[test] + fn overflowing_should_allow_updating() { + let identity_key = AccountHash::new([1; 32]); + let identity_key_weight = Weight::new(1); + let key_1 = AccountHash::new([2u8; 32]); + let key_1_weight = Weight::new(3); + let key_2 = AccountHash::new([3u8; 32]); + let key_2_weight = Weight::new(255); + let deployment_threshold = Weight::new(1); + let key_management_threshold = Weight::new(254); + + let associated_keys = { + // Identity + let mut res = AssociatedKeys::new(identity_key, identity_key_weight); + + // Spare key + res.add_key(key_1, key_1_weight).expect("should add key 1"); + // Big key + res.add_key(key_2, key_2_weight).expect("should add key 2"); + + res + }; + + let mut account = Account::new( + identity_key, + NamedKeys::new(), + URef::new([0u8; 32], AccessRights::READ_ADD_WRITE), + associated_keys, + ActionThresholds::new(deployment_threshold, key_management_threshold) + .expect("should create thresholds"), + ); + + // decrease so total weight would be changed from 1 + 3 + 255 to 1 + 1 + 255 + account + .update_associated_key(key_1, Weight::new(1)) + .expect("should work"); + } +} diff --git a/node/src/contract_shared/account/action_thresholds.rs b/node/src/contract_shared/account/action_thresholds.rs new file mode 100644 index 0000000000..3c1fe4beb9 --- /dev/null +++ b/node/src/contract_shared/account/action_thresholds.rs @@ -0,0 +1,150 @@ +use types::{ + account::{ActionType, SetThresholdFailure, Weight, WEIGHT_SERIALIZED_LENGTH}, + bytesrepr::{self, Error, FromBytes, ToBytes}, +}; + +/// Thresholds that have to be met when executing an action of a certain type. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ActionThresholds { + deployment: Weight, + key_management: Weight, +} + +impl ActionThresholds { + /// Creates new ActionThresholds object with provided weights + /// + /// Requires deployment threshold to be lower than or equal to + /// key management threshold. + pub fn new( + deployment: Weight, + key_management: Weight, + ) -> Result { + if deployment > key_management { + return Err(SetThresholdFailure::DeploymentThreshold); + } + Ok(ActionThresholds { + deployment, + key_management, + }) + } + /// Sets new threshold for [ActionType::Deployment]. + /// Should return an error if setting new threshold for `action_type` breaks + /// one of the invariants. Currently, invariant is that + /// `ActionType::Deployment` threshold shouldn't be higher than any + /// other, which should be checked both when increasing `Deployment` + /// threshold and decreasing the other. + pub fn set_deployment_threshold( + &mut self, + new_threshold: Weight, + ) -> Result<(), SetThresholdFailure> { + if new_threshold > self.key_management { + Err(SetThresholdFailure::DeploymentThreshold) + } else { + self.deployment = new_threshold; + Ok(()) + } + } + + /// Sets new threshold for [ActionType::KeyManagement]. + pub fn set_key_management_threshold( + &mut self, + new_threshold: Weight, + ) -> Result<(), SetThresholdFailure> { + if self.deployment > new_threshold { + Err(SetThresholdFailure::KeyManagementThreshold) + } else { + self.key_management = new_threshold; + Ok(()) + } + } + + pub fn deployment(&self) -> &Weight { + &self.deployment + } + + pub fn key_management(&self) -> &Weight { + &self.key_management + } + + /// Unified function that takes an action type, and changes appropriate + /// threshold defined by the [ActionType] variants. + pub fn set_threshold( + &mut self, + action_type: ActionType, + new_threshold: Weight, + ) -> Result<(), SetThresholdFailure> { + match action_type { + ActionType::Deployment => self.set_deployment_threshold(new_threshold), + ActionType::KeyManagement => self.set_key_management_threshold(new_threshold), + } + } +} + +impl Default for ActionThresholds { + fn default() -> Self { + ActionThresholds { + deployment: Weight::new(1), + key_management: Weight::new(1), + } + } +} + +impl ToBytes for ActionThresholds { + fn to_bytes(&self) -> Result, Error> { + let mut result = bytesrepr::unchecked_allocate_buffer(self); + result.append(&mut self.deployment.to_bytes()?); + result.append(&mut self.key_management.to_bytes()?); + Ok(result) + } + + fn serialized_length(&self) -> usize { + 2 * WEIGHT_SERIALIZED_LENGTH + } +} + +impl FromBytes for ActionThresholds { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (deployment, rem) = Weight::from_bytes(&bytes)?; + let (key_management, rem) = Weight::from_bytes(&rem)?; + let ret = ActionThresholds { + deployment, + key_management, + }; + Ok((ret, rem)) + } +} + +#[cfg(any(feature = "gens", test))] +pub mod gens { + use proptest::prelude::*; + + use super::ActionThresholds; + + pub fn action_thresholds_arb() -> impl Strategy { + Just(Default::default()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_create_new_action_thresholds() { + let action_thresholds = ActionThresholds::new(Weight::new(1), Weight::new(42)).unwrap(); + assert_eq!(*action_thresholds.deployment(), Weight::new(1)); + assert_eq!(*action_thresholds.key_management(), Weight::new(42)); + } + + #[test] + fn should_not_create_action_thresholds_with_invalid_deployment_threshold() { + // deployment cant be greater than key management + assert!(ActionThresholds::new(Weight::new(5), Weight::new(1)).is_err()); + } + + #[test] + fn serialization_roundtrip() { + let action_thresholds = ActionThresholds::new(Weight::new(1), Weight::new(42)).unwrap(); + bytesrepr::test_serialization_roundtrip(&action_thresholds); + } +} diff --git a/node/src/contract_shared/account/associated_keys.rs b/node/src/contract_shared/account/associated_keys.rs new file mode 100644 index 0000000000..dc21c5852d --- /dev/null +++ b/node/src/contract_shared/account/associated_keys.rs @@ -0,0 +1,329 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use types::{ + account::{ + AccountHash, AddKeyFailure, RemoveKeyFailure, UpdateKeyFailure, Weight, MAX_ASSOCIATED_KEYS, + }, + bytesrepr::{Error, FromBytes, ToBytes}, +}; + +#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Clone, Debug)] +pub struct AssociatedKeys(BTreeMap); + +impl AssociatedKeys { + pub fn new(key: AccountHash, weight: Weight) -> AssociatedKeys { + let mut bt: BTreeMap = BTreeMap::new(); + bt.insert(key, weight); + AssociatedKeys(bt) + } + + /// Adds new AssociatedKey to the set. + /// Returns true if added successfully, false otherwise. + #[allow(clippy::map_entry)] + pub fn add_key(&mut self, key: AccountHash, weight: Weight) -> Result<(), AddKeyFailure> { + if self.0.len() == MAX_ASSOCIATED_KEYS { + Err(AddKeyFailure::MaxKeysLimit) + } else if self.0.contains_key(&key) { + Err(AddKeyFailure::DuplicateKey) + } else { + self.0.insert(key, weight); + Ok(()) + } + } + + /// Removes key from the associated keys set. + /// Returns true if value was found in the set prior to the removal, false + /// otherwise. + pub fn remove_key(&mut self, key: &AccountHash) -> Result<(), RemoveKeyFailure> { + self.0 + .remove(key) + .map(|_| ()) + .ok_or(RemoveKeyFailure::MissingKey) + } + + /// Adds new AssociatedKey to the set. + /// Returns true if added successfully, false otherwise. + #[allow(clippy::map_entry)] + pub fn update_key(&mut self, key: AccountHash, weight: Weight) -> Result<(), UpdateKeyFailure> { + if !self.0.contains_key(&key) { + return Err(UpdateKeyFailure::MissingKey); + } + + self.0.insert(key, weight); + Ok(()) + } + + pub fn get(&self, key: &AccountHash) -> Option<&Weight> { + self.0.get(key) + } + + pub fn contains_key(&self, key: &AccountHash) -> bool { + self.0.contains_key(key) + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Helper method that calculates weight for keys that comes from any + /// source. + /// + /// This method is not concerned about uniqueness of the passed iterable. + /// Uniqueness is determined based on the input collection properties, + /// which is either BTreeSet (in [`AssociatedKeys::calculate_keys_weight`]) + /// or BTreeMap (in [`AssociatedKeys::total_keys_weight`]). + fn calculate_any_keys_weight<'a>(&self, keys: impl Iterator) -> Weight { + let total = keys + .filter_map(|key| self.0.get(key)) + .fold(0u8, |acc, w| acc.saturating_add(w.value())); + + Weight::new(total) + } + + /// Calculates total weight of authorization keys provided by an argument + pub fn calculate_keys_weight(&self, authorization_keys: &BTreeSet) -> Weight { + self.calculate_any_keys_weight(authorization_keys.iter()) + } + + /// Calculates total weight of all authorization keys + pub fn total_keys_weight(&self) -> Weight { + self.calculate_any_keys_weight(self.0.keys()) + } + + /// Calculates total weight of all authorization keys excluding a given key + pub fn total_keys_weight_excluding(&self, account_hash: AccountHash) -> Weight { + self.calculate_any_keys_weight(self.0.keys().filter(|&&element| element != account_hash)) + } +} + +impl ToBytes for AssociatedKeys { + fn to_bytes(&self) -> Result, Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } +} + +impl FromBytes for AssociatedKeys { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (keys_map, rem) = BTreeMap::::from_bytes(bytes)?; + let mut keys = AssociatedKeys::default(); + keys_map.into_iter().for_each(|(k, v)| { + // NOTE: we're ignoring potential errors (duplicate key, maximum number of + // elements). This is safe, for now, as we were the ones that + // serialized `AssociatedKeys` in the first place. + keys.add_key(k, v).unwrap(); + }); + Ok((keys, rem)) + } +} + +#[cfg(any(feature = "gens", test))] +pub mod gens { + use proptest::prelude::*; + + use types::gens::{account_hash_arb, weight_arb}; + + use super::AssociatedKeys; + + pub fn associated_keys_arb(size: usize) -> impl Strategy { + proptest::collection::btree_map(account_hash_arb(), weight_arb(), size).prop_map(|keys| { + let mut associated_keys = AssociatedKeys::default(); + keys.into_iter().for_each(|(k, v)| { + associated_keys.add_key(k, v).unwrap(); + }); + associated_keys + }) + } +} + +#[cfg(test)] +mod tests { + use std::{collections::BTreeSet, iter::FromIterator}; + + use types::{ + account::{AccountHash, AddKeyFailure, Weight, ACCOUNT_HASH_LENGTH, MAX_ASSOCIATED_KEYS}, + bytesrepr, + }; + + use super::AssociatedKeys; + + #[test] + fn associated_keys_add() { + let mut keys = + AssociatedKeys::new(AccountHash::new([0u8; ACCOUNT_HASH_LENGTH]), Weight::new(1)); + let new_pk = AccountHash::new([1u8; ACCOUNT_HASH_LENGTH]); + let new_pk_weight = Weight::new(2); + assert!(keys.add_key(new_pk, new_pk_weight).is_ok()); + assert_eq!(keys.get(&new_pk), Some(&new_pk_weight)) + } + + #[test] + fn associated_keys_add_full() { + let map = (0..MAX_ASSOCIATED_KEYS).map(|k| { + ( + AccountHash::new([k as u8; ACCOUNT_HASH_LENGTH]), + Weight::new(k as u8), + ) + }); + assert_eq!(map.len(), 10); + let mut keys = { + let mut tmp = AssociatedKeys::default(); + map.for_each(|(key, weight)| assert!(tmp.add_key(key, weight).is_ok())); + tmp + }; + assert_eq!( + keys.add_key( + AccountHash::new([100u8; ACCOUNT_HASH_LENGTH]), + Weight::new(100) + ), + Err(AddKeyFailure::MaxKeysLimit) + ) + } + + #[test] + fn associated_keys_add_duplicate() { + let pk = AccountHash::new([0u8; ACCOUNT_HASH_LENGTH]); + let weight = Weight::new(1); + let mut keys = AssociatedKeys::new(pk, weight); + assert_eq!( + keys.add_key(pk, Weight::new(10)), + Err(AddKeyFailure::DuplicateKey) + ); + assert_eq!(keys.get(&pk), Some(&weight)); + } + + #[test] + fn associated_keys_remove() { + let pk = AccountHash::new([0u8; ACCOUNT_HASH_LENGTH]); + let weight = Weight::new(1); + let mut keys = AssociatedKeys::new(pk, weight); + assert!(keys.remove_key(&pk).is_ok()); + assert!(keys + .remove_key(&AccountHash::new([1u8; ACCOUNT_HASH_LENGTH])) + .is_err()); + } + + #[test] + fn associated_keys_calculate_keys_once() { + let key_1 = AccountHash::new([0; 32]); + let key_2 = AccountHash::new([1; 32]); + let key_3 = AccountHash::new([2; 32]); + let mut keys = AssociatedKeys::default(); + + keys.add_key(key_2, Weight::new(2)) + .expect("should add key_1"); + keys.add_key(key_1, Weight::new(1)) + .expect("should add key_1"); + keys.add_key(key_3, Weight::new(3)) + .expect("should add key_1"); + + assert_eq!( + keys.calculate_keys_weight(&BTreeSet::from_iter(vec![ + key_1, key_2, key_3, key_1, key_2, key_3, + ])), + Weight::new(1 + 2 + 3) + ); + } + + #[test] + fn associated_keys_total_weight() { + let associated_keys = { + let mut res = AssociatedKeys::new(AccountHash::new([1u8; 32]), Weight::new(1)); + res.add_key(AccountHash::new([2u8; 32]), Weight::new(11)) + .expect("should add key 1"); + res.add_key(AccountHash::new([3u8; 32]), Weight::new(12)) + .expect("should add key 2"); + res.add_key(AccountHash::new([4u8; 32]), Weight::new(13)) + .expect("should add key 3"); + res + }; + assert_eq!( + associated_keys.total_keys_weight(), + Weight::new(1 + 11 + 12 + 13) + ); + } + + #[test] + fn associated_keys_total_weight_excluding() { + let identity_key = AccountHash::new([1u8; 32]); + let identity_key_weight = Weight::new(1); + + let key_1 = AccountHash::new([2u8; 32]); + let key_1_weight = Weight::new(11); + + let key_2 = AccountHash::new([3u8; 32]); + let key_2_weight = Weight::new(12); + + let key_3 = AccountHash::new([4u8; 32]); + let key_3_weight = Weight::new(13); + + let associated_keys = { + let mut res = AssociatedKeys::new(identity_key, identity_key_weight); + res.add_key(key_1, key_1_weight).expect("should add key 1"); + res.add_key(key_2, key_2_weight).expect("should add key 2"); + res.add_key(key_3, key_3_weight).expect("should add key 3"); + res + }; + assert_eq!( + associated_keys.total_keys_weight_excluding(key_2), + Weight::new(identity_key_weight.value() + key_1_weight.value() + key_3_weight.value()) + ); + } + + #[test] + fn overflowing_keys_weight() { + let identity_key = AccountHash::new([1u8; 32]); + let key_1 = AccountHash::new([2u8; 32]); + let key_2 = AccountHash::new([3u8; 32]); + let key_3 = AccountHash::new([4u8; 32]); + + let identity_key_weight = Weight::new(250); + let weight_1 = Weight::new(1); + let weight_2 = Weight::new(2); + let weight_3 = Weight::new(3); + + let saturated_weight = Weight::new(u8::max_value()); + + let associated_keys = { + let mut res = AssociatedKeys::new(identity_key, identity_key_weight); + + res.add_key(key_1, weight_1).expect("should add key 1"); + res.add_key(key_2, weight_2).expect("should add key 2"); + res.add_key(key_3, weight_3).expect("should add key 3"); + res + }; + + assert_eq!( + associated_keys.calculate_keys_weight(&BTreeSet::from_iter(vec![ + identity_key, // 250 + key_1, // 251 + key_2, // 253 + key_3, // 256 - error + ])), + saturated_weight, + ); + } + + #[test] + fn serialization_roundtrip() { + let mut keys = AssociatedKeys::default(); + keys.add_key(AccountHash::new([1; 32]), Weight::new(1)) + .unwrap(); + keys.add_key(AccountHash::new([2; 32]), Weight::new(2)) + .unwrap(); + keys.add_key(AccountHash::new([3; 32]), Weight::new(3)) + .unwrap(); + bytesrepr::test_serialization_roundtrip(&keys); + } +} diff --git a/node/src/contract_shared/additive_map.rs b/node/src/contract_shared/additive_map.rs new file mode 100644 index 0000000000..b761e4bcd8 --- /dev/null +++ b/node/src/contract_shared/additive_map.rs @@ -0,0 +1,169 @@ +use std::{ + borrow::Borrow, + collections::{ + hash_map::{IntoIter, Iter, IterMut, Keys, RandomState, Values}, + HashMap, + }, + fmt::{self, Debug, Formatter}, + hash::{BuildHasher, Hash}, + iter::{FromIterator, IntoIterator}, + ops::{AddAssign, Index}, +}; + +#[derive(Clone)] +pub struct AdditiveMap(HashMap); + +impl AdditiveMap { + pub fn new() -> Self { + Self(Default::default()) + } +} + +impl AdditiveMap { + /// Modifies the existing value stored under `key`, or the default value for `V` if none, by + /// adding `value_to_add`. + pub fn insert_add(&mut self, key: K, value_to_add: V) { + let current_value = self.0.entry(key).or_default(); + *current_value += value_to_add; + } +} + +impl AdditiveMap { + pub fn keys(&self) -> Keys<'_, K, V> { + self.0.keys() + } + + pub fn values(&self) -> Values<'_, K, V> { + self.0.values() + } + + pub fn iter(&self) -> Iter<'_, K, V> { + self.0.iter() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl AdditiveMap { + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Eq + Hash + ?Sized, + { + self.0.get(key) + } + + pub fn insert(&mut self, key: K, value: V) -> Option { + self.0.insert(key, value) + } + + pub fn remove(&mut self, key: &Q) -> Option + where + K: Borrow, + Q: Eq + Hash + ?Sized, + { + self.0.remove(key) + } + + pub fn remove_entry(&mut self, key: &Q) -> Option<(K, V)> + where + K: Borrow, + Q: Eq + Hash + ?Sized, + { + self.0.remove_entry(key) + } +} + +impl Default for AdditiveMap { + fn default() -> Self { + Self(HashMap::with_hasher(Default::default())) + } +} + +impl<'a, K, V, S> IntoIterator for &'a AdditiveMap { + type Item = (&'a K, &'a V); + type IntoIter = Iter<'a, K, V>; + + fn into_iter(self) -> Iter<'a, K, V> { + self.0.iter() + } +} + +impl<'a, K, V, S> IntoIterator for &'a mut AdditiveMap { + type Item = (&'a K, &'a mut V); + type IntoIter = IterMut<'a, K, V>; + + fn into_iter(self) -> IterMut<'a, K, V> { + self.0.iter_mut() + } +} + +impl IntoIterator for AdditiveMap { + type Item = (K, V); + type IntoIter = IntoIter; + + fn into_iter(self) -> IntoIter { + self.0.into_iter() + } +} + +impl FromIterator<(K, V)> for AdditiveMap { + fn from_iter>(iter: T) -> Self { + Self(HashMap::from_iter(iter)) + } +} + +impl Index<&Q> for AdditiveMap +where + K: Eq + Hash + Borrow, + Q: Eq + Hash + ?Sized, + S: BuildHasher, +{ + type Output = V; + + fn index(&self, key: &Q) -> &V { + &self.0[key] + } +} + +impl PartialEq for AdditiveMap { + fn eq(&self, other: &AdditiveMap) -> bool { + self.0 == other.0 + } +} + +impl Eq for AdditiveMap {} + +impl Debug for AdditiveMap { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +#[cfg(test)] +mod tests { + use super::AdditiveMap; + use crate::contract_shared::transform::Transform; + + #[test] + fn insert_add() { + let key = "key"; + let mut int_map = AdditiveMap::new(); + int_map.insert_add(key, 1); + assert_eq!(1, int_map[key]); + int_map.insert_add(key, 2); + assert_eq!(3, int_map[key]); + + let mut transform_map = AdditiveMap::new(); + transform_map.insert_add(key, Transform::AddUInt64(1)); + assert_eq!(Transform::AddUInt64(1), transform_map[key]); + transform_map.insert_add(key, Transform::AddInt32(2)); + assert_eq!(Transform::AddInt32(3), transform_map[key]); + } +} diff --git a/node/src/contract_shared/gas.rs b/node/src/contract_shared/gas.rs new file mode 100644 index 0000000000..e41aaa3ce6 --- /dev/null +++ b/node/src/contract_shared/gas.rs @@ -0,0 +1,186 @@ +use std::fmt; + +use num::Zero; + +use types::U512; + +use crate::contract_shared::motes::Motes; + +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct Gas(U512); + +impl Gas { + pub fn new(value: U512) -> Self { + Gas(value) + } + + pub fn value(&self) -> U512 { + self.0 + } + + pub fn from_motes(motes: Motes, conv_rate: u64) -> Option { + motes + .value() + .checked_div(U512::from(conv_rate)) + .map(Self::new) + } + + pub fn checked_add(&self, rhs: Self) -> Option { + self.0.checked_add(rhs.value()).map(Self::new) + } +} + +impl fmt::Display for Gas { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl std::ops::Add for Gas { + type Output = Gas; + + fn add(self, rhs: Self) -> Self::Output { + let val = self.value() + rhs.value(); + Gas::new(val) + } +} + +impl std::ops::Sub for Gas { + type Output = Gas; + + fn sub(self, rhs: Self) -> Self::Output { + let val = self.value() - rhs.value(); + Gas::new(val) + } +} + +impl std::ops::Div for Gas { + type Output = Gas; + + fn div(self, rhs: Self) -> Self::Output { + let val = self.value() / rhs.value(); + Gas::new(val) + } +} + +impl std::ops::Mul for Gas { + type Output = Gas; + + fn mul(self, rhs: Self) -> Self::Output { + let val = self.value() * rhs.value(); + Gas::new(val) + } +} + +impl Zero for Gas { + fn zero() -> Self { + Gas::new(U512::zero()) + } + + fn is_zero(&self) -> bool { + self.0.is_zero() + } +} + +#[cfg(test)] +mod tests { + use types::U512; + + use crate::contract_shared::{gas::Gas, motes::Motes}; + + #[test] + fn should_be_able_to_get_instance_of_gas() { + let initial_value = 1; + let gas = Gas::new(U512::from(initial_value)); + assert_eq!( + initial_value, + gas.value().as_u64(), + "should have equal value" + ) + } + + #[test] + fn should_be_able_to_compare_two_instances_of_gas() { + let left_gas = Gas::new(U512::from(1)); + let right_gas = Gas::new(U512::from(1)); + assert_eq!(left_gas, right_gas, "should be equal"); + let right_gas = Gas::new(U512::from(2)); + assert_ne!(left_gas, right_gas, "should not be equal") + } + + #[test] + fn should_be_able_to_add_two_instances_of_gas() { + let left_gas = Gas::new(U512::from(1)); + let right_gas = Gas::new(U512::from(1)); + let expected_gas = Gas::new(U512::from(2)); + assert_eq!((left_gas + right_gas), expected_gas, "should be equal") + } + + #[test] + fn should_be_able_to_subtract_two_instances_of_gas() { + let left_gas = Gas::new(U512::from(1)); + let right_gas = Gas::new(U512::from(1)); + let expected_gas = Gas::new(U512::from(0)); + assert_eq!((left_gas - right_gas), expected_gas, "should be equal") + } + + #[test] + fn should_be_able_to_multiply_two_instances_of_gas() { + let left_gas = Gas::new(U512::from(100)); + let right_gas = Gas::new(U512::from(10)); + let expected_gas = Gas::new(U512::from(1000)); + assert_eq!((left_gas * right_gas), expected_gas, "should be equal") + } + + #[test] + fn should_be_able_to_divide_two_instances_of_gas() { + let left_gas = Gas::new(U512::from(1000)); + let right_gas = Gas::new(U512::from(100)); + let expected_gas = Gas::new(U512::from(10)); + assert_eq!((left_gas / right_gas), expected_gas, "should be equal") + } + + #[test] + fn should_be_able_to_convert_from_mote() { + let mote = Motes::new(U512::from(100)); + let gas = Gas::from_motes(mote, 10).expect("should have gas"); + let expected_gas = Gas::new(U512::from(10)); + assert_eq!(gas, expected_gas, "should be equal") + } + + #[test] + fn should_be_able_to_default() { + let gas = Gas::default(); + let expected_gas = Gas::new(U512::from(0)); + assert_eq!(gas, expected_gas, "should be equal") + } + + #[test] + fn should_be_able_to_compare_relative_value() { + let left_gas = Gas::new(U512::from(100)); + let right_gas = Gas::new(U512::from(10)); + assert!(left_gas > right_gas, "should be gt"); + let right_gas = Gas::new(U512::from(100)); + assert!(left_gas >= right_gas, "should be gte"); + assert!(left_gas <= right_gas, "should be lte"); + let left_gas = Gas::new(U512::from(10)); + assert!(left_gas < right_gas, "should be lt"); + } + + #[test] + fn should_default() { + let left_gas = Gas::new(U512::from(0)); + let right_gas = Gas::default(); + assert_eq!(left_gas, right_gas, "should be equal"); + let u512 = U512::zero(); + assert_eq!(left_gas.value(), u512, "should be equal"); + } + + #[test] + fn should_support_checked_div_from_motes() { + let motes = Motes::new(U512::zero()); + let conv_rate = 0; + let maybe = Gas::from_motes(motes, conv_rate); + assert!(maybe.is_none(), "should be none due to divide by zero"); + } +} diff --git a/node/src/contract_shared/logging/README.md b/node/src/contract_shared/logging/README.md new file mode 100644 index 0000000000..d0b61ce29a --- /dev/null +++ b/node/src/contract_shared/logging/README.md @@ -0,0 +1,50 @@ +# Logging + +## General + +The `logging` module provides the ability to log messages from any CasperLabs crate to `stdout` using the canonical +macros from the [`log` crate](https://crates.io/crates/log). + +It also provides functions to allow logging messages with properties attached for the purpose of structured logging and +integration with tools like [Prometheus](https://prometheus.io/). + +Logging can be initialized to support outputting metrics, regardless of the chosen log-level, and can also be set to +display messages in a human-readable format or a hybrid structured one, with each line containing a human-readable +component followed by JSON formatted details. + +## Usage + +#### In libraries + +Libraries should link only to the `log` crate, and use the provided macros to log whatever information will be useful to +downstream consumers. + +#### In executables + +Logging can be initialized using the [`initialize()`][initialize] function, and should be done early in the runtime of +the program. + +#### In tests + +Logging can also be initialized early in a test's execution. Note that constructing a +[`TestContextBuilder`][TestContextBuilder] will automatically enable logging at warn-level in the human-readable +format. To avoid this, call [`initialize()`][initialize] with required settings before constructing the first +`TestContextBuilder`. + +Bear in mind that by default tests are run in parallel on multiple threads, so initializing logging might need to be +done in several or all of the tests in a single binary. + +## Metrics + +The structured log messages output via [`log_metric()`][log_metric] or [`log_duration()`][log_duration] can be +parsed and read by the [`casperlabs-engine-metrics-scraper`][scraper]. + +This tool reads from `stdin`, extracts the "time-series-data" from the log messages' properties and makes the values +available via a `GET` endpoint. + + +[initialize]: https://docs.rs/casperlabs-engine-shared/latest/casperlabs_engine_shared/logging/fn.initialize.html +[log_metric]: https://docs.rs/casperlabs-engine-shared/latest/casperlabs_engine_shared/logging/fn.log_metric.html +[log_duration]: https://docs.rs/casperlabs-engine-shared/latest/casperlabs_engine_shared/logging/fn.log_duration.html +[TestContextBuilder]: https://docs.rs/casperlabs-engine-test-support/latest/casperlabs_engine_test_support/struct.TestContextBuilder.html +[scraper]: https://github.com/CasperLabs/CasperLabs/tree/master/execution-engine/engine-metrics-scraper diff --git a/node/src/contract_shared/logging/mod.rs b/node/src/contract_shared/logging/mod.rs new file mode 100644 index 0000000000..da0bcc04c9 --- /dev/null +++ b/node/src/contract_shared/logging/mod.rs @@ -0,0 +1,188 @@ +//! A logger implementation which outputs log messages from CasperLabs crates to the terminal. + +mod settings; +mod structured_message; +mod terminal_logger; + +use std::{ + collections::BTreeMap, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use log::{self, Level, LevelFilter, Log, Metadata, Record, SetLoggerError}; + +pub use self::terminal_logger::TerminalLogger; +use crate::contract_shared::newtypes::CorrelationId; +pub use settings::{Settings, Style}; + +#[doc(hidden)] +pub const PAYLOAD_KEY: &str = "payload="; +pub(crate) const METRIC_METADATA_TARGET: &str = "METRIC"; +pub(crate) const CASPERLABS_METADATA_TARGET: &str = "casperlabs_"; +pub(crate) const MESSAGE_TEMPLATE_KEY: &str = "message_template"; +pub(crate) const DEFAULT_MESSAGE_TEMPLATE: &str = "{message}"; +pub(crate) const DEFAULT_MESSAGE_KEY: &str = "message"; + +/// Initializes the global logger using the given settings. +/// +/// The logger will write all log messages from crates prefixed with "casperlabs_" to stdout, and +/// can also log internal metrics generated by the Execution Engine. +/// +/// Returns an error if the global logger has already been set in this process. +pub fn initialize(settings: Settings) -> Result<(), SetLoggerError> { + let logger = Box::new(TerminalLogger::new(&settings)); + initialize_with_logger(logger, settings) +} + +/// This and the `TerminalLogger` are public but undocumented to allow functional testing of this +/// crate, e.g. by passing a logger composed of a `TerminalLogger`. +#[doc(hidden)] +pub fn initialize_with_logger( + logger: Box, + settings: Settings, +) -> Result<(), SetLoggerError> { + if settings.max_level() == LevelFilter::Off && !settings.enable_metrics() { + // No logging required + return Ok(()); + } + + log::set_boxed_logger(logger)?; + log::set_max_level(settings.max_level()); + Ok(()) +} + +/// Logs a message using the given format and properties. +/// +/// # Arguments +/// +/// * `log_level` - log level of the message to be logged +/// * `message_format` - a message template to apply over properties by key +/// * `properties` - a collection of machine readable key / value properties which will be logged +#[inline] +pub fn log_details( + log_level: Level, + message_format: String, + mut properties: BTreeMap<&str, String>, +) { + let logger = log::logger(); + + let metadata = Metadata::builder() + .target(CASPERLABS_METADATA_TARGET) + .level(log_level) + .build(); + + if !logger.enabled(&metadata) { + return; + } + + properties.insert(MESSAGE_TEMPLATE_KEY, message_format); + + let record = Record::builder() + .metadata(metadata) + .key_values(&properties) + .build(); + + logger.log(&record); +} + +/// Logs the duration of a specific operation. +/// +/// # Arguments +/// +/// * `correlation_id` - a shared identifier used to group metrics +/// * `metric` - the name of the metric +/// * `tag` - a grouping tag for the metric +/// * `duration` - in seconds +#[inline] +pub fn log_duration(correlation_id: CorrelationId, metric: &str, tag: &str, duration: Duration) { + let duration_in_seconds: f64 = duration.as_secs_f64(); + + log_metric( + correlation_id, + metric, + tag, + "duration_in_seconds", + duration_in_seconds, + ) +} + +/// Logs the details of the specified metric. +/// +/// # Arguments +/// +/// * `correlation_id` - a shared identifier used to group metrics +/// * `metric` - the name of the metric +/// * `tag` - a grouping tag for the metric +/// * `metric_key` - property key for metric's value +/// * `metric_value` - numeric value of metric +#[inline] +pub fn log_metric( + correlation_id: CorrelationId, + metric: &str, + tag: &str, + metric_key: &str, + metric_value: f64, +) { + let logger = log::logger(); + + let metadata = Metadata::builder() + .target(METRIC_METADATA_TARGET) + .level(Level::Info) + .build(); + + if !logger.enabled(&metadata) { + return; + } + + let from_epoch = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("UNIX EPOCH ERROR"); + + let milliseconds_since_epoch = from_epoch.as_millis() as i64; + + // https://prometheus.io/docs/instrumenting/exposition_formats/ + let tsd_metric = format!( + "{}{{tag=\"{}\"}} {} {:?}", + metric, tag, metric_value, milliseconds_since_epoch + ); + + let mut properties = BTreeMap::new(); + properties.insert("correlation_id", correlation_id.to_string()); + properties.insert("time-series-data", tsd_metric); + properties.insert(metric_key, format!("{:?}", metric_value)); + properties.insert( + DEFAULT_MESSAGE_KEY, + format!("{} {} {}", metric, tag, metric_value), + ); + + let record = Record::builder() + .metadata(metadata) + .key_values(&properties) + .build(); + + logger.log(&record); +} + +/// Logs the metrics associated with the specified host function. +pub fn log_host_function_metrics(host_function: &str, mut properties: BTreeMap<&str, String>) { + let logger = log::logger(); + + let metadata = Metadata::builder() + .target(METRIC_METADATA_TARGET) + .level(Level::Info) + .build(); + + if !logger.enabled(&metadata) { + return; + } + + let default_message = format!("{} {:?}", host_function, properties); + properties.insert(DEFAULT_MESSAGE_KEY, default_message); + + let record = Record::builder() + .metadata(metadata) + .key_values(&properties) + .build(); + + logger.log(&record); +} diff --git a/node/src/contract_shared/logging/settings.rs b/node/src/contract_shared/logging/settings.rs new file mode 100644 index 0000000000..68d2986758 --- /dev/null +++ b/node/src/contract_shared/logging/settings.rs @@ -0,0 +1,63 @@ +use log::LevelFilter; + +/// Settings used to initialize the global logger. +#[derive(Clone, Copy, Debug)] +pub struct Settings { + max_level: LevelFilter, + enable_metrics: bool, + style: Style, +} + +impl Settings { + /// Constructs new `Settings`, where `max_level` sets the verbosity level above which messages + /// will be filtered out. + /// + /// `Off` is the lowest level, through `Error`, `Warn`, `Info`, `Debug` to `Trace` at the + /// highest level. + /// + /// By default, logging of metrics is disabled (see + /// [`with_metrics_enabled()`](Settings::with_metrics_enabled)), and the logging-style is set + /// to [`Style::Structured`]. + pub fn new(max_level: LevelFilter) -> Self { + Settings { + max_level, + enable_metrics: false, + style: Style::Structured, + } + } + + /// If `true`, log messages created via [`log_metric()`](crate::logging::log_metric) and + /// [`log_duration()`](crate::logging::log_duration) are logged, regardless of the log-level. + pub fn with_metrics_enabled(mut self, value: bool) -> Self { + self.enable_metrics = value; + self + } + + /// Sets the logging style to structured or human-readable. + pub fn with_style(mut self, value: Style) -> Self { + self.style = value; + self + } + + pub(crate) fn max_level(&self) -> LevelFilter { + self.max_level + } + + pub(crate) fn enable_metrics(&self) -> bool { + self.enable_metrics + } + + pub(crate) fn style(&self) -> Style { + self.style + } +} + +/// The style of generated log messages. +#[derive(Clone, Copy, Debug)] +pub enum Style { + /// Hybrid structured log-messages, with a human-readable component followed by JSON formatted + /// details. + Structured, + /// Human-readable log-messages. + HumanReadable, +} diff --git a/node/src/contract_shared/logging/structured_message.rs b/node/src/contract_shared/logging/structured_message.rs new file mode 100644 index 0000000000..6f21ae6fb9 --- /dev/null +++ b/node/src/contract_shared/logging/structured_message.rs @@ -0,0 +1,453 @@ +use std::{ + collections::BTreeMap, + env, + fmt::{self, Display, Formatter}, + process, +}; + +use chrono::{DateTime, SecondsFormat, Utc}; +use lazy_static::lazy_static; +use log::kv::{self, Key, Value, Visitor}; +use serde::{Serialize, Serializer}; + +use types::SemVer; + +use crate::contract_shared::{ + logging::{DEFAULT_MESSAGE_TEMPLATE, MESSAGE_TEMPLATE_KEY}, + utils, +}; + +lazy_static! { + static ref PROCESS_ID: u32 = process::id(); + static ref PROCESS_NAME: String = env::current_exe() + .ok() + .and_then(|full_path| { + full_path + .file_stem() + .map(|file_stem| file_stem.to_string_lossy().to_string()) + }) + .unwrap_or_else(|| "unknown-process".to_string()); + static ref HOST_NAME: String = hostname::get() + .map(|host_name| host_name.to_string_lossy().to_string()) + .unwrap_or_else(|_| "unknown-host".to_string()); + static ref MESSAGE_TYPE: String = "ee-structured".to_string(); + static ref MESSAGE_TYPE_VERSION: MessageTypeVersion = MessageTypeVersion::default(); +} + +/// container for log message data +#[derive(Clone, Debug, Serialize)] +pub(crate) struct StructuredMessage { + timestamp: TimestampRfc3999, + process_id: u32, + process_name: String, + host_name: String, + log_level: String, + priority: Priority, + message_type: String, + message_type_version: MessageTypeVersion, + message_id: MessageId, + description: String, + properties: MessageProperties, +} + +impl StructuredMessage { + pub fn new(log_level: String, message_id: MessageId, properties: MessageProperties) -> Self { + let timestamp = TimestampRfc3999::default(); + let process_id = *PROCESS_ID; + let process_name = PROCESS_NAME.clone(); + let host_name = HOST_NAME.clone(); + let priority = Priority::from(log_level.as_str()); + let message_type = MESSAGE_TYPE.clone(); + let message_type_version = *MESSAGE_TYPE_VERSION; + let description = properties.get_formatted_message(); + + StructuredMessage { + timestamp, + process_id, + process_name, + host_name, + log_level, + priority, + message_type, + message_type_version, + message_id, + description, + properties, + } + } +} + +impl Display for StructuredMessage { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + let json = utils::jsonify(self, false); + write!( + formatter, + "{timestamp} {loglevel} {priority} {hostname} {facility} payload={payload}", + timestamp = self.timestamp, + loglevel = self.log_level.to_string().to_uppercase(), + priority = self.priority, + hostname = self.host_name, + facility = self.process_name, + payload = json + ) + } +} + +/// newtype to encapsulate log level priority +#[derive(Clone, Copy, Debug, Hash, Serialize)] +struct Priority(u8); + +impl From<&str> for Priority { + fn from(level: &str) -> Self { + match level { + "Error" => Priority(3), + "Warn" => Priority(4), + "Info" => Priority(5), + "Debug" => Priority(6), + "Metric" => Priority(6), + "Trace" => Priority(7), + _ => Priority(255), + } + } +} + +impl Display for Priority { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + self.0.fmt(formatter) + } +} + +#[derive(Debug, Copy, Clone)] +struct MessageTypeVersion(SemVer); + +impl Display for MessageTypeVersion { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + self.0.fmt(formatter) + } +} + +impl Default for MessageTypeVersion { + fn default() -> Self { + MessageTypeVersion(SemVer::V1_0_0) + } +} + +impl Serialize for MessageTypeVersion { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = format!("{}.{}.{}", self.0.major, self.0.minor, self.0.patch); + serializer.serialize_str(&s) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize)] +pub(crate) struct MessageId(usize); + +impl MessageId { + pub fn new(id: usize) -> MessageId { + MessageId(id) + } +} + +/// newtype for Rfc3999 formatted timestamp +#[derive(Clone, Debug, Hash, Serialize)] +pub(crate) struct TimestampRfc3999(String); + +impl Default for TimestampRfc3999 { + fn default() -> Self { + let now: DateTime = Utc::now(); + TimestampRfc3999(now.to_rfc3339_opts(SecondsFormat::Millis, true)) + } +} + +impl Display for TimestampRfc3999 { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + self.0.fmt(formatter) + } +} + +#[derive(Clone, Debug, Hash, Serialize)] +pub(crate) struct MessageProperties(BTreeMap); + +impl MessageProperties { + pub fn new(mut properties: BTreeMap) -> MessageProperties { + // add the default message template ("message_template", "{message}") if the template key + // doesn't already exist. + properties + .entry(MESSAGE_TEMPLATE_KEY.to_string()) + .or_insert_with(|| DEFAULT_MESSAGE_TEMPLATE.to_string()); + MessageProperties(properties) + } + + pub fn insert(&mut self, key: String, value: String) -> Option { + self.0.insert(key, value) + } + + /// strips out brace encased motes in message_template + /// and applies them as candidate keys for the encapsulated collection of + /// message properties. the underlying value of any candidate key that + /// has an entry in the collection will be spliced into the output in + /// the place of its corresponding brace encased candidate key + pub fn get_formatted_message(&self) -> String { + let message_template = match self.0.get(MESSAGE_TEMPLATE_KEY) { + Some(message_template) if !message_template.is_empty() => message_template, + _ => return String::new(), + }; + + let mut buf = String::new(); + let mut candidate_key = String::new(); + + let mut key_seek = false; + let properties = &self.0; + + for c in message_template.chars() { + match c { + '{' => { + key_seek = true; + candidate_key.clear(); + } + '}' if key_seek => { + key_seek = false; + if let Some(v) = properties.get(&candidate_key) { + buf.push_str(v); + } + } + '}' => (), + c if key_seek => candidate_key.push(c), + c => buf.push(c), + } + } + buf + } +} + +impl Default for MessageProperties { + fn default() -> Self { + MessageProperties::new(BTreeMap::new()) + } +} + +/// This impl allows us to populate a `MessageProperties` map from a log `Record::key_values()`. +impl<'kvs> Visitor<'kvs> for MessageProperties { + fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), kv::Error> { + // The value was generated via the Debug impl, i.e. it has been wrapped in quotation marks + // and inner chars have been escaped as required. Undo this for passing to `jsonify`, or we + // get double-escaped chars. + let value = value + .to_string() + .trim_matches('"') + .replace(r#"\'"#, r#"'"#) + .replace(r#"\""#, r#"""#) + .replace(r#"\\"#, r#"\"#); + self.0.insert(key.to_string(), value); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::contract_shared::logging::DEFAULT_MESSAGE_KEY; + + #[test] + fn should_get_process_id() { + assert!( + *PROCESS_ID != 0, + "PROCESS_ID should not be 0: {}", + *PROCESS_ID + ); + } + + #[test] + fn should_get_process_name() { + assert!(!PROCESS_NAME.is_empty(), "PROCESS_NAME should have chars") + } + + #[test] + fn should_get_host_name() { + assert!(!HOST_NAME.is_empty(), "HOST_NAME should have chars") + } + + #[test] + fn should_format_message_template_default_use_case() { + let mut properties: BTreeMap = BTreeMap::new(); + properties.insert( + DEFAULT_MESSAGE_KEY.to_string(), + "i am a log message".to_string(), + ); + + let props = MessageProperties::new(properties); + + let formatted = props.get_formatted_message(); + + assert_eq!( + formatted, + "i am a log message".to_string(), + "message malformed" + ) + } + + #[test] + fn should_format_message_template_starting_and_ending_with_braces() { + let mut properties: BTreeMap = BTreeMap::new(); + properties.insert( + DEFAULT_MESSAGE_KEY.to_string(), + "i convey meaning".to_string(), + ); + properties.insert("abc".to_string(), "some text".to_string()); + properties.insert("some-hash".to_string(), "A@#$!@#".to_string()); + properties.insert("byz".to_string(), "".to_string()); + let template = + "{abc} i'm a message temp{byz}late some-hash:{some-hash} msg:{message}".to_string(); + properties.insert(MESSAGE_TEMPLATE_KEY.to_string(), template); + + let props = MessageProperties::new(properties); + + let formatted = props.get_formatted_message(); + + assert_eq!( + formatted, + "some text i\'m a message template some-hash:A@#$!@# msg:i convey meaning".to_string(), + "message malformed" + ) + } + + #[test] + fn should_format_message_template_with_escaped_braces() { + let mut properties: BTreeMap = BTreeMap::new(); + properties.insert(DEFAULT_MESSAGE_KEY.to_string(), "a message".to_string()); + properties.insert("more-data".to_string(), "some additional data".to_string()); + let template = "this is {{message}} with {{{more-data}}}".to_string(); + properties.insert(MESSAGE_TEMPLATE_KEY.to_string(), template); + + let props = MessageProperties::new(properties); + + let formatted = props.get_formatted_message(); + + assert_eq!( + formatted, + "this is a message with some additional data".to_string(), + "message malformed" + ) + } + + #[test] + fn should_format_message_template_with_no_properties() { + let properties: BTreeMap = BTreeMap::new(); + + let props = MessageProperties::new(properties); + + let formatted = props.get_formatted_message(); + + assert_eq!(formatted, "".to_string(), "message malformed") + } + + #[test] + fn should_format_message_template_with_unclosed_brace() { + let mut properties: BTreeMap = BTreeMap::new(); + let template = "{message".to_string(); + properties.insert(MESSAGE_TEMPLATE_KEY.to_string(), template); + + let props = MessageProperties::new(properties); + + let formatted = props.get_formatted_message(); + + assert_eq!(formatted, "".to_string(), "message malformed") + } + + #[test] + fn should_format_message_template_with_unopened_brace() { + let mut properties: BTreeMap = BTreeMap::new(); + let template = "message}".to_string(); + properties.insert(MESSAGE_TEMPLATE_KEY.to_string(), template); + + let props = MessageProperties::new(properties); + + let formatted = props.get_formatted_message(); + + assert_eq!(formatted, "message".to_string(), "message malformed") + } + + #[test] + fn should_format_message_template_with_mismatched_braces_left() { + let mut properties: BTreeMap = BTreeMap::new(); + let template = "{{message}".to_string(); + properties.insert(MESSAGE_TEMPLATE_KEY.to_string(), template); + + let props = MessageProperties::new(properties); + + let formatted = props.get_formatted_message(); + + assert_eq!(formatted, "".to_string(), "message malformed") + } + + #[test] + fn should_format_message_template_with_mismatched_braces_right() { + let mut properties: BTreeMap = BTreeMap::new(); + let template = "{message}}".to_string(); + properties.insert(MESSAGE_TEMPLATE_KEY.to_string(), template); + + let props = MessageProperties::new(properties); + + let formatted = props.get_formatted_message(); + + assert_eq!(formatted, "".to_string(), "message malformed") + } + + #[test] + fn should_validate_log_message() { + let test_msg = "test_message".to_string(); + + let mut properties = MessageProperties::default(); + properties.insert(DEFAULT_MESSAGE_KEY.to_string(), test_msg); + + let l = StructuredMessage::new("Error".to_string(), MessageId::new(1), properties); + + assert!( + should_have_rfc3339_timestamp(&l), + "rfc3339 timestamp required" + ); + + assert!(should_have_log_level(&l), "log level required"); + assert!(should_have_process_id(&l), "process id required"); + assert!(should_have_process_name(&l), "process name required"); + assert!(should_have_host_name(&l), "host name required"); + assert!(should_have_at_least_one_property(&l), "properties required"); + assert!(should_have_description(&l), "description required"); + } + + fn should_have_rfc3339_timestamp(l: &StructuredMessage) -> bool { + // ISO 8601 / RFC 3339 + // rfc3339 = "YYYY-MM-DDTHH:mm:ss+00:00" + match DateTime::parse_from_rfc3339(&l.timestamp.0) { + Ok(_d) => true, + Err(_) => false, + } + } + + fn should_have_log_level(l: &StructuredMessage) -> bool { + !l.log_level.is_empty() + } + + fn should_have_description(l: &StructuredMessage) -> bool { + !l.description.is_empty() + } + + fn should_have_process_id(l: &StructuredMessage) -> bool { + l.process_id > 0 + } + + fn should_have_process_name(l: &StructuredMessage) -> bool { + !l.process_name.is_empty() + } + + fn should_have_host_name(l: &StructuredMessage) -> bool { + !l.host_name.is_empty() + } + + fn should_have_at_least_one_property(l: &StructuredMessage) -> bool { + !l.properties.0.is_empty() + } +} diff --git a/node/src/contract_shared/logging/terminal_logger.rs b/node/src/contract_shared/logging/terminal_logger.rs new file mode 100644 index 0000000000..dc2b43e853 --- /dev/null +++ b/node/src/contract_shared/logging/terminal_logger.rs @@ -0,0 +1,110 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +use log::{Level, LevelFilter, Log, Metadata, Record}; + +use crate::contract_shared::logging::{ + structured_message::{MessageId, MessageProperties, StructuredMessage, TimestampRfc3999}, + Settings, Style, CASPERLABS_METADATA_TARGET, DEFAULT_MESSAGE_KEY, METRIC_METADATA_TARGET, +}; + +#[doc(hidden)] +/// Logs messages from targets with prefix "casperlabs_" or "METRIC" to stdout. +pub struct TerminalLogger { + max_level: LevelFilter, + metrics_enabled: bool, + style: Style, + next_message_id: AtomicUsize, +} + +impl TerminalLogger { + pub fn new(settings: &Settings) -> Self { + TerminalLogger { + max_level: settings.max_level(), + metrics_enabled: settings.enable_metrics(), + style: settings.style(), + next_message_id: AtomicUsize::new(0), + } + } + + pub fn prepare_log_line(&self, record: &Record) -> Option { + if !self.enabled(&record.metadata()) { + return None; + } + + let mut properties = MessageProperties::default(); + let _ = record.key_values().visit(&mut properties); + + let log_line = match self.style { + Style::Structured => { + if record.key_values().count() == 0 { + properties.insert( + DEFAULT_MESSAGE_KEY.to_string(), + format!("{}", record.args()), + ); + } + + let message_id = + MessageId::new(self.next_message_id.fetch_add(1, Ordering::SeqCst)); + let structured_message = StructuredMessage::new( + level_to_str(record).to_string(), + message_id, + properties, + ); + format!("{}", structured_message) + } + Style::HumanReadable => { + let formatted_properties = properties.get_formatted_message(); + let msg = format!("{}", record.args()); + format!( + "{timestamp} {level} [{file}:{line}] {msg}{space}{formatted_properties}", + timestamp = TimestampRfc3999::default(), + level = level_to_str(&record).to_uppercase(), + file = record.file().unwrap_or_else(|| "unknown-file"), + line = record.line().unwrap_or_default(), + msg = msg, + space = if formatted_properties.is_empty() || msg.is_empty() { + "" + } else { + " " + }, + formatted_properties = formatted_properties + ) + } + }; + + Some(log_line) + } +} + +impl Log for TerminalLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + // If the target starts "casperlabs_" it's either come from a log macro in one of our + // crates, or via `logging::log_details`. In this case, check the level. + (metadata.target().starts_with(CASPERLABS_METADATA_TARGET) + && metadata.level() <= self.max_level) + // Otherwise, check if the target is "METRIC" and if we have metric logging enabled. + || (self.metrics_enabled && metadata.target() == METRIC_METADATA_TARGET) + } + + fn log(&self, record: &Record) { + if let Some(log_line) = self.prepare_log_line(record) { + println!("{}", log_line); + } + } + + fn flush(&self) {} +} + +fn level_to_str<'a>(record: &'a Record) -> &'a str { + if record.target() == METRIC_METADATA_TARGET { + return "Metric"; + } + + match record.level() { + Level::Trace => "Trace", + Level::Debug => "Debug", + Level::Info => "Info", + Level::Warn => "Warn", + Level::Error => "Error", + } +} diff --git a/node/src/contract_shared/motes.rs b/node/src/contract_shared/motes.rs new file mode 100644 index 0000000000..0f29f58cca --- /dev/null +++ b/node/src/contract_shared/motes.rs @@ -0,0 +1,201 @@ +use std::fmt; + +use num::Zero; + +use types::U512; + +use crate::contract_shared::gas::Gas; + +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct Motes(U512); + +impl Motes { + pub fn new(value: U512) -> Motes { + Motes(value) + } + + pub fn checked_add(&self, rhs: Self) -> Option { + self.0.checked_add(rhs.value()).map(Self::new) + } + + pub fn value(&self) -> U512 { + self.0 + } + + pub fn from_gas(gas: Gas, conv_rate: u64) -> Option { + gas.value() + .checked_mul(U512::from(conv_rate)) + .map(Self::new) + } +} + +impl fmt::Display for Motes { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl std::ops::Add for Motes { + type Output = Motes; + + fn add(self, rhs: Self) -> Self::Output { + let val = self.value() + rhs.value(); + Motes::new(val) + } +} + +impl std::ops::Sub for Motes { + type Output = Motes; + + fn sub(self, rhs: Self) -> Self::Output { + let val = self.value() - rhs.value(); + Motes::new(val) + } +} + +impl std::ops::Div for Motes { + type Output = Motes; + + fn div(self, rhs: Self) -> Self::Output { + let val = self.value() / rhs.value(); + Motes::new(val) + } +} + +impl std::ops::Mul for Motes { + type Output = Motes; + + fn mul(self, rhs: Self) -> Self::Output { + let val = self.value() * rhs.value(); + Motes::new(val) + } +} + +impl Zero for Motes { + fn zero() -> Self { + Motes::new(U512::zero()) + } + + fn is_zero(&self) -> bool { + self.0.is_zero() + } +} + +#[cfg(test)] +mod tests { + use types::U512; + + use crate::contract_shared::{gas::Gas, motes::Motes}; + + #[test] + fn should_be_able_to_get_instance_of_motes() { + let initial_value = 1; + let motes = Motes::new(U512::from(initial_value)); + assert_eq!( + initial_value, + motes.value().as_u64(), + "should have equal value" + ) + } + + #[test] + fn should_be_able_to_compare_two_instances_of_motes() { + let left_motes = Motes::new(U512::from(1)); + let right_motes = Motes::new(U512::from(1)); + assert_eq!(left_motes, right_motes, "should be equal"); + let right_motes = Motes::new(U512::from(2)); + assert_ne!(left_motes, right_motes, "should not be equal") + } + + #[test] + fn should_be_able_to_add_two_instances_of_motes() { + let left_motes = Motes::new(U512::from(1)); + let right_motes = Motes::new(U512::from(1)); + let expected_motes = Motes::new(U512::from(2)); + assert_eq!( + (left_motes + right_motes), + expected_motes, + "should be equal" + ) + } + + #[test] + fn should_be_able_to_subtract_two_instances_of_motes() { + let left_motes = Motes::new(U512::from(1)); + let right_motes = Motes::new(U512::from(1)); + let expected_motes = Motes::new(U512::from(0)); + assert_eq!( + (left_motes - right_motes), + expected_motes, + "should be equal" + ) + } + + #[test] + fn should_be_able_to_multiply_two_instances_of_motes() { + let left_motes = Motes::new(U512::from(100)); + let right_motes = Motes::new(U512::from(10)); + let expected_motes = Motes::new(U512::from(1000)); + assert_eq!( + (left_motes * right_motes), + expected_motes, + "should be equal" + ) + } + + #[test] + fn should_be_able_to_divide_two_instances_of_motes() { + let left_motes = Motes::new(U512::from(1000)); + let right_motes = Motes::new(U512::from(100)); + let expected_motes = Motes::new(U512::from(10)); + assert_eq!( + (left_motes / right_motes), + expected_motes, + "should be equal" + ) + } + + #[test] + fn should_be_able_to_convert_from_motes() { + let gas = Gas::new(U512::from(100)); + let motes = Motes::from_gas(gas, 10).expect("should have value"); + let expected_motes = Motes::new(U512::from(1000)); + assert_eq!(motes, expected_motes, "should be equal") + } + + #[test] + fn should_be_able_to_default() { + let motes = Motes::default(); + let expected_motes = Motes::new(U512::from(0)); + assert_eq!(motes, expected_motes, "should be equal") + } + + #[test] + fn should_be_able_to_compare_relative_value() { + let left_motes = Motes::new(U512::from(100)); + let right_motes = Motes::new(U512::from(10)); + assert!(left_motes > right_motes, "should be gt"); + let right_motes = Motes::new(U512::from(100)); + assert!(left_motes >= right_motes, "should be gte"); + assert!(left_motes <= right_motes, "should be lte"); + let left_motes = Motes::new(U512::from(10)); + assert!(left_motes < right_motes, "should be lt"); + } + + #[test] + fn should_default() { + let left_motes = Motes::new(U512::from(0)); + let right_motes = Motes::default(); + assert_eq!(left_motes, right_motes, "should be equal"); + let u512 = U512::zero(); + assert_eq!(left_motes.value(), u512, "should be equal"); + } + + #[test] + fn should_support_checked_mul_from_gas() { + let gas = Gas::new(U512::MAX); + let conv_rate = 10; + let maybe = Motes::from_gas(gas, conv_rate); + assert!(maybe.is_none(), "should be none due to overflow"); + } +} diff --git a/node/src/contract_shared/newtypes/macros.rs b/node/src/contract_shared/newtypes/macros.rs new file mode 100644 index 0000000000..6d4131bbb9 --- /dev/null +++ b/node/src/contract_shared/newtypes/macros.rs @@ -0,0 +1,119 @@ +/// Creates an array newtype for given length with special access operators already implemented. +#[macro_export] +macro_rules! make_array_newtype { + ($name:ident, $ty:ty, $len:expr) => { + pub struct $name([$ty; $len]); + + impl $name { + pub fn new(source: [$ty; $len]) -> Self { + $name(source) + } + + pub fn into_inner(self) -> [$ty; $len] { + self.0 + } + } + + impl Clone for $name { + fn clone(&self) -> $name { + let &$name(ref dat) = self; + $name(dat.clone()) + } + } + + impl Copy for $name {} + + impl PartialEq for $name { + fn eq(&self, other: &$name) -> bool { + &self[..] == &other[..] + } + } + + impl Eq for $name {} + + impl PartialOrd for $name { + fn partial_cmp(&self, other: &$name) -> Option { + Some(self.cmp(other)) + } + } + + impl Ord for $name { + fn cmp(&self, other: &$name) -> core::cmp::Ordering { + self.0.cmp(&other.0) + } + } + + impl core::ops::Index for $name { + type Output = $ty; + + fn index(&self, index: usize) -> &$ty { + let &$name(ref dat) = self; + &dat[index] + } + } + + impl core::ops::Index> for $name { + type Output = [$ty]; + + fn index(&self, index: core::ops::Range) -> &[$ty] { + let &$name(ref dat) = self; + &dat[index] + } + } + + impl core::ops::Index> for $name { + type Output = [$ty]; + + fn index(&self, index: core::ops::RangeTo) -> &[$ty] { + let &$name(ref dat) = self; + &dat[index] + } + } + + impl core::ops::Index> for $name { + type Output = [$ty]; + + fn index(&self, index: core::ops::RangeFrom) -> &[$ty] { + let &$name(ref dat) = self; + &dat[index] + } + } + + impl core::ops::Index for $name { + type Output = [$ty]; + + fn index(&self, _: core::ops::RangeFull) -> &[$ty] { + let &$name(ref dat) = self; + &dat[..] + } + } + + impl core::fmt::Debug for $name { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + write!(f, "{}([", stringify!($name))?; + write!(f, "{:?}", self.0[0])?; + for item in self.0[1..].iter() { + write!(f, ", {:?}", item)?; + } + write!(f, "])") + } + } + + impl bytesrepr::ToBytes for $name { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } + } + + impl bytesrepr::FromBytes for $name { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (dat, rem) = <[$ty; $len]>::from_bytes(bytes)?; + Ok(($name(dat), rem)) + } + } + }; +} diff --git a/node/src/contract_shared/newtypes/mod.rs b/node/src/contract_shared/newtypes/mod.rs new file mode 100644 index 0000000000..9ee90bd2fd --- /dev/null +++ b/node/src/contract_shared/newtypes/mod.rs @@ -0,0 +1,286 @@ +//! Some newtypes. +mod macros; + +use core::array::TryFromSliceError; +use std::{convert::TryFrom, fmt}; + +use blake2::{ + digest::{Input, VariableOutput}, + VarBlake2b, +}; +use serde::Serialize; +use uuid::Uuid; + +use types::bytesrepr::{self, FromBytes, ToBytes}; + +pub use types::BLAKE2B_DIGEST_LENGTH; + +/// Represents a 32-byte BLAKE2b hash digest +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Blake2bHash([u8; BLAKE2B_DIGEST_LENGTH]); + +impl Blake2bHash { + /// Creates a 32-byte BLAKE2b hash digest from a given a piece of data + pub fn new(data: &[u8]) -> Self { + let mut ret = [0u8; BLAKE2B_DIGEST_LENGTH]; + // Safe to unwrap here because our digest length is constant and valid + let mut hasher = VarBlake2b::new(BLAKE2B_DIGEST_LENGTH).unwrap(); + hasher.input(data); + hasher.variable_result(|hash| ret.clone_from_slice(hash)); + Blake2bHash(ret) + } + + /// Returns the underlying BLKAE2b hash bytes + pub fn value(&self) -> [u8; BLAKE2B_DIGEST_LENGTH] { + self.0 + } + + /// Converts the underlying BLAKE2b hash digest array to a `Vec` + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } +} + +impl core::fmt::LowerHex for Blake2bHash { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let hex_string = base16::encode_lower(&self.value()); + if f.alternate() { + write!(f, "0x{}", hex_string) + } else { + write!(f, "{}", hex_string) + } + } +} + +impl core::fmt::UpperHex for Blake2bHash { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let hex_string = base16::encode_upper(&self.value()); + if f.alternate() { + write!(f, "0x{}", hex_string) + } else { + write!(f, "{}", hex_string) + } + } +} + +impl core::fmt::Display for Blake2bHash { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "Blake2bHash({:#x})", self) + } +} + +impl core::fmt::Debug for Blake2bHash { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", self) + } +} + +impl From<[u8; BLAKE2B_DIGEST_LENGTH]> for Blake2bHash { + fn from(arr: [u8; BLAKE2B_DIGEST_LENGTH]) -> Self { + Blake2bHash(arr) + } +} + +impl<'a> TryFrom<&'a [u8]> for Blake2bHash { + type Error = TryFromSliceError; + + fn try_from(slice: &[u8]) -> Result { + <[u8; BLAKE2B_DIGEST_LENGTH]>::try_from(slice).map(Blake2bHash) + } +} + +impl Into<[u8; BLAKE2B_DIGEST_LENGTH]> for Blake2bHash { + fn into(self) -> [u8; BLAKE2B_DIGEST_LENGTH] { + self.0 + } +} + +impl ToBytes for Blake2bHash { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } +} + +impl FromBytes for Blake2bHash { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + FromBytes::from_bytes(bytes).map(|(arr, rem)| (Blake2bHash(arr), rem)) + } +} + +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Serialize)] +pub struct CorrelationId(Uuid); + +impl CorrelationId { + pub fn new() -> CorrelationId { + CorrelationId(Uuid::new_v4()) + } + + pub fn is_empty(&self) -> bool { + self.0.is_nil() + } +} + +impl fmt::Display for CorrelationId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +#[cfg(test)] +mod tests { + use crate::contract_shared::{ + newtypes::{Blake2bHash, CorrelationId}, + utils, + }; + use std::hash::{Hash, Hasher}; + + #[test] + fn should_be_able_to_generate_correlation_id() { + let correlation_id = CorrelationId::new(); + + assert_ne!( + correlation_id.to_string(), + "00000000-0000-0000-0000-000000000000", + "should not be empty value" + ) + } + + #[test] + fn should_support_to_string() { + let correlation_id = CorrelationId::new(); + + assert!( + !correlation_id.is_empty(), + "correlation_id should be produce string" + ) + } + + #[test] + fn should_support_to_string_no_type_encasement() { + let correlation_id = CorrelationId::new(); + + let correlation_id_string = correlation_id.to_string(); + + assert!( + !correlation_id_string.starts_with("CorrelationId"), + "correlation_id should just be the inner value without tuple name" + ) + } + + #[test] + fn should_support_to_json() { + let correlation_id = CorrelationId::new(); + + let correlation_id_json = utils::jsonify(correlation_id, false); + + assert!( + !correlation_id_json.is_empty(), + "correlation_id should be produce json" + ) + } + + #[test] + fn should_support_is_display() { + let correlation_id = CorrelationId::new(); + + let display = format!("{}", correlation_id); + + assert!(!display.is_empty(), "display should not be empty") + } + + #[test] + fn should_support_is_empty() { + let correlation_id = CorrelationId::new(); + + assert!( + !correlation_id.is_empty(), + "correlation_id should not be empty" + ) + } + + #[test] + fn should_create_unique_id_on_new() { + let correlation_id_lhs = CorrelationId::new(); + let correlation_id_rhs = CorrelationId::new(); + + assert_ne!( + correlation_id_lhs, correlation_id_rhs, + "correlation_ids should be distinct" + ); + } + + #[test] + fn should_support_clone() { + let correlation_id = CorrelationId::new(); + + let cloned = correlation_id; + + assert_eq!(correlation_id, cloned, "should be cloneable") + } + + #[test] + fn should_support_copy() { + let correlation_id = CorrelationId::new(); + + let cloned = correlation_id; + + assert_eq!(correlation_id, cloned, "should be cloneable") + } + + #[test] + fn should_support_hash() { + let correlation_id = CorrelationId::new(); + + let mut state = std::collections::hash_map::DefaultHasher::new(); + + correlation_id.hash(&mut state); + + let hash = state.finish(); + + assert!(hash > 0, "should be hashable"); + } + + #[test] + fn should_display_blake2bhash_in_hex() { + let hash = Blake2bHash([0u8; 32]); + let hash_hex = format!("{}", hash); + assert_eq!( + hash_hex, + "Blake2bHash(0x0000000000000000000000000000000000000000000000000000000000000000)" + ); + } + + #[test] + fn should_print_blake2bhash_lower_hex() { + let hash = Blake2bHash([10u8; 32]); + let hash_lower_hex = format!("{:x}", hash); + assert_eq!( + hash_lower_hex, + "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a" + ) + } + + #[test] + fn should_print_blake2bhash_upper_hex() { + let hash = Blake2bHash([10u8; 32]); + let hash_lower_hex = format!("{:X}", hash); + assert_eq!( + hash_lower_hex, + "0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A" + ) + } + + #[test] + fn alternate_should_prepend_0x() { + let hash = Blake2bHash([0u8; 32]); + let hash_hex_alt = format!("{:#x}", hash); + assert_eq!( + hash_hex_alt, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ) + } +} diff --git a/node/src/contract_shared/page_size.rs b/node/src/contract_shared/page_size.rs new file mode 100644 index 0000000000..ee0f611cf1 --- /dev/null +++ b/node/src/contract_shared/page_size.rs @@ -0,0 +1,15 @@ +use std::io; + +use libc::{c_long, sysconf, _SC_PAGESIZE}; + +/// Returns OS page size +pub fn get_page_size() -> Result { + // https://www.gnu.org/software/libc/manual/html_node/Sysconf.html + let value: c_long = unsafe { sysconf(_SC_PAGESIZE) }; + + if value < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(value as usize) +} diff --git a/node/src/contract_shared/socket.rs b/node/src/contract_shared/socket.rs new file mode 100644 index 0000000000..ced94cd805 --- /dev/null +++ b/node/src/contract_shared/socket.rs @@ -0,0 +1,34 @@ +use std::{io, path::Path}; + +pub struct Socket(String); + +impl Socket { + pub fn new(socket: String) -> Self { + Socket(socket) + } + + pub fn value(&self) -> String { + self.0.clone() + } + + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + pub fn get_path(&self) -> &Path { + std::path::Path::new(&self.0) + } + + /// Safely removes file pointed out by a path. + /// + /// In practice this tries to remove the file, and if + /// the file does not exist, it ignores it, and propagates + /// any other error. + pub fn remove_file(&self) -> io::Result<()> { + let path = self.get_path(); + match std::fs::remove_file(path) { + Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(()), + result => result, + } + } +} diff --git a/node/src/contract_shared/stored_value.rs b/node/src/contract_shared/stored_value.rs new file mode 100644 index 0000000000..99849f9f63 --- /dev/null +++ b/node/src/contract_shared/stored_value.rs @@ -0,0 +1,237 @@ +use std::convert::TryFrom; + +use types::{ + bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + contracts::ContractPackage, + CLValue, Contract, ContractWasm, +}; + +use crate::contract_shared::{account::Account, TypeMismatch}; + +#[repr(u8)] +enum Tag { + CLValue = 0, + Account = 1, + ContractWasm = 2, + Contract = 3, + ContractPackage = 4, +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum StoredValue { + CLValue(CLValue), + Account(Account), + ContractWasm(ContractWasm), + Contract(Contract), + ContractPackage(ContractPackage), +} + +impl StoredValue { + pub fn as_cl_value(&self) -> Option<&CLValue> { + match self { + StoredValue::CLValue(cl_value) => Some(cl_value), + _ => None, + } + } + + pub fn as_account(&self) -> Option<&Account> { + match self { + StoredValue::Account(account) => Some(account), + _ => None, + } + } + + pub fn as_contract(&self) -> Option<&Contract> { + match self { + StoredValue::Contract(contract) => Some(contract), + _ => None, + } + } + + pub fn as_contract_wasm(&self) -> Option<&ContractWasm> { + match self { + StoredValue::ContractWasm(contract_wasm) => Some(contract_wasm), + _ => None, + } + } + + pub fn as_contract_package(&self) -> Option<&ContractPackage> { + match self { + StoredValue::ContractPackage(contract_package) => Some(&contract_package), + _ => None, + } + } + + pub fn type_name(&self) -> String { + match self { + StoredValue::CLValue(cl_value) => format!("{:?}", cl_value.cl_type()), + StoredValue::Account(_) => "Account".to_string(), + StoredValue::ContractWasm(_) => "Contract".to_string(), + StoredValue::Contract(_) => "Contract".to_string(), + StoredValue::ContractPackage(_) => "ContractPackage".to_string(), + } + } +} + +impl TryFrom for CLValue { + type Error = TypeMismatch; + + fn try_from(stored_value: StoredValue) -> Result { + match stored_value { + StoredValue::CLValue(cl_value) => Ok(cl_value), + _ => Err(TypeMismatch::new( + "CLValue".to_string(), + stored_value.type_name(), + )), + } + } +} + +impl TryFrom for Account { + type Error = TypeMismatch; + + fn try_from(stored_value: StoredValue) -> Result { + match stored_value { + StoredValue::Account(account) => Ok(account), + _ => Err(TypeMismatch::new( + "Account".to_string(), + stored_value.type_name(), + )), + } + } +} + +impl TryFrom for ContractWasm { + type Error = TypeMismatch; + + fn try_from(stored_value: StoredValue) -> Result { + match stored_value { + StoredValue::ContractWasm(contract_wasm) => Ok(contract_wasm), + _ => Err(TypeMismatch::new( + "ContractWasm".to_string(), + stored_value.type_name(), + )), + } + } +} + +impl TryFrom for ContractPackage { + type Error = TypeMismatch; + + fn try_from(stored_value: StoredValue) -> Result { + match stored_value { + StoredValue::ContractPackage(contract_package) => Ok(contract_package), + _ => Err(TypeMismatch::new( + "ContractPackage".to_string(), + stored_value.type_name(), + )), + } + } +} + +impl TryFrom for Contract { + type Error = TypeMismatch; + + fn try_from(stored_value: StoredValue) -> Result { + match stored_value { + StoredValue::Contract(contract) => Ok(contract), + _ => Err(TypeMismatch::new( + "Contract".to_string(), + stored_value.type_name(), + )), + } + } +} + +impl ToBytes for StoredValue { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut result = bytesrepr::allocate_buffer(self)?; + let (tag, mut serialized_data) = match self { + StoredValue::CLValue(cl_value) => (Tag::CLValue, cl_value.to_bytes()?), + StoredValue::Account(account) => (Tag::Account, account.to_bytes()?), + StoredValue::ContractWasm(contract_wasm) => { + (Tag::ContractWasm, contract_wasm.to_bytes()?) + } + StoredValue::Contract(contract_header) => (Tag::Contract, contract_header.to_bytes()?), + StoredValue::ContractPackage(contract_package) => { + (Tag::ContractPackage, contract_package.to_bytes()?) + } + }; + result.push(tag as u8); + result.append(&mut serialized_data); + Ok(result) + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + + match self { + StoredValue::CLValue(cl_value) => cl_value.serialized_length(), + StoredValue::Account(account) => account.serialized_length(), + StoredValue::ContractWasm(contract_wasm) => contract_wasm.serialized_length(), + StoredValue::Contract(contract_header) => contract_header.serialized_length(), + StoredValue::ContractPackage(contract_package) => { + contract_package.serialized_length() + } + } + } +} + +impl FromBytes for StoredValue { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, remainder): (u8, &[u8]) = FromBytes::from_bytes(bytes)?; + match tag { + tag if tag == Tag::CLValue as u8 => CLValue::from_bytes(remainder) + .map(|(cl_value, remainder)| (StoredValue::CLValue(cl_value), remainder)), + tag if tag == Tag::Account as u8 => Account::from_bytes(remainder) + .map(|(account, remainder)| (StoredValue::Account(account), remainder)), + tag if tag == Tag::ContractWasm as u8 => { + ContractWasm::from_bytes(remainder).map(|(contract_wasm, remainder)| { + (StoredValue::ContractWasm(contract_wasm), remainder) + }) + } + tag if tag == Tag::ContractPackage as u8 => { + ContractPackage::from_bytes(remainder).map(|(contract_package, remainder)| { + (StoredValue::ContractPackage(contract_package), remainder) + }) + } + tag if tag == Tag::Contract as u8 => Contract::from_bytes(remainder) + .map(|(contract, remainder)| (StoredValue::Contract(contract), remainder)), + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +#[cfg(any(feature = "gens", test))] +pub mod gens { + use proptest::prelude::*; + + use types::gens::cl_value_arb; + + use super::StoredValue; + use crate::contract_shared::account::gens::account_arb; + use types::gens::{contract_arb, contract_package_arb, contract_wasm_arb}; + + pub fn stored_value_arb() -> impl Strategy { + prop_oneof![ + cl_value_arb().prop_map(StoredValue::CLValue), + account_arb().prop_map(StoredValue::Account), + contract_package_arb().prop_map(StoredValue::ContractPackage), + contract_arb().prop_map(StoredValue::Contract), + contract_wasm_arb().prop_map(StoredValue::ContractWasm), + ] + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use super::*; + + proptest! { + #[test] + fn serialization_roundtrip(v in gens::stored_value_arb()) { + bytesrepr::test_serialization_roundtrip(&v); + } + } +} diff --git a/node/src/contract_shared/test_utils.rs b/node/src/contract_shared/test_utils.rs new file mode 100644 index 0000000000..1f6f2a99d3 --- /dev/null +++ b/node/src/contract_shared/test_utils.rs @@ -0,0 +1,43 @@ +//! Some functions to use in tests. + +use types::{account::AccountHash, contracts::NamedKeys, AccessRights, Key, URef}; + +use crate::contract_shared::wasm_costs::WasmCosts; +use crate::contract_shared::{account::Account, stored_value::StoredValue}; + +/// Returns an account value paired with its key +pub fn mocked_account(account_hash: AccountHash) -> Vec<(Key, StoredValue)> { + let purse = URef::new([0u8; 32], AccessRights::READ_ADD_WRITE); + let account = Account::create(account_hash, NamedKeys::new(), purse); + vec![(Key::Account(account_hash), StoredValue::Account(account))] +} + +pub fn wasm_costs_mock() -> WasmCosts { + WasmCosts { + regular: 1, + div: 16, + mul: 4, + mem: 2, + initial_mem: 4096, + grow_mem: 8192, + memcpy: 1, + max_stack_height: 64 * 1024, + opcodes_mul: 3, + opcodes_div: 8, + } +} + +pub fn wasm_costs_free() -> WasmCosts { + WasmCosts { + regular: 0, + div: 0, + mul: 0, + mem: 0, + initial_mem: 4096, + grow_mem: 8192, + memcpy: 0, + max_stack_height: 64 * 1024, + opcodes_mul: 1, + opcodes_div: 1, + } +} diff --git a/node/src/contract_shared/transform.rs b/node/src/contract_shared/transform.rs new file mode 100644 index 0000000000..c2cf4648de --- /dev/null +++ b/node/src/contract_shared/transform.rs @@ -0,0 +1,750 @@ +use std::{ + any, + convert::TryFrom, + default::Default, + fmt::{self, Display, Formatter}, + ops::{Add, AddAssign}, +}; + +use num::traits::{AsPrimitive, WrappingAdd}; + +use types::{ + bytesrepr::{self, FromBytes, ToBytes}, + contracts::NamedKeys, + CLType, CLTyped, CLValue, CLValueError, U128, U256, U512, +}; + +use crate::contract_shared::{stored_value::StoredValue, TypeMismatch}; + +/// Error type for applying and combining transforms. A `TypeMismatch` +/// occurs when a transform cannot be applied because the types are +/// not compatible (e.g. trying to add a number to a string). An +/// `Overflow` occurs if addition between numbers would result in the +/// value overflowing its size in memory (e.g. if a, b are i32 and a + +/// b > i32::MAX then a `AddInt32(a).apply(Value::Int32(b))` would +/// cause an overflow). +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum Error { + Serialization(bytesrepr::Error), + TypeMismatch(TypeMismatch), +} + +impl From for Error { + fn from(t: TypeMismatch) -> Error { + Error::TypeMismatch(t) + } +} + +impl From for Error { + fn from(cl_value_error: CLValueError) -> Error { + match cl_value_error { + CLValueError::Serialization(error) => Error::Serialization(error), + CLValueError::Type(cl_type_mismatch) => { + let expected = format!("{:?}", cl_type_mismatch.expected); + let found = format!("{:?}", cl_type_mismatch.found); + let type_mismatch = TypeMismatch { expected, found }; + Error::TypeMismatch(type_mismatch) + } + } + } +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum Transform { + Identity, + Write(StoredValue), + AddInt32(i32), + AddUInt64(u64), + AddUInt128(U128), + AddUInt256(U256), + AddUInt512(U512), + AddKeys(NamedKeys), + Failure(Error), +} + +macro_rules! from_try_from_impl { + ($type:ty, $variant:ident) => { + impl From<$type> for Transform { + fn from(x: $type) -> Self { + Transform::$variant(x) + } + } + + impl TryFrom for $type { + type Error = String; + + fn try_from(t: Transform) -> Result<$type, String> { + match t { + Transform::$variant(x) => Ok(x), + other => Err(format!("{:?}", other)), + } + } + } + }; +} + +from_try_from_impl!(StoredValue, Write); +from_try_from_impl!(i32, AddInt32); +from_try_from_impl!(u64, AddUInt64); +from_try_from_impl!(U128, AddUInt128); +from_try_from_impl!(U256, AddUInt256); +from_try_from_impl!(U512, AddUInt512); +from_try_from_impl!(NamedKeys, AddKeys); +from_try_from_impl!(Error, Failure); + +/// Attempts a wrapping addition of `to_add` to `stored_value`, assuming `stored_value` is +/// compatible with type `Y`. +fn wrapping_addition(stored_value: StoredValue, to_add: Y) -> Result +where + Y: AsPrimitive + + AsPrimitive + + AsPrimitive + + AsPrimitive + + AsPrimitive + + AsPrimitive + + AsPrimitive + + AsPrimitive, +{ + let cl_value = CLValue::try_from(stored_value)?; + + match cl_value.cl_type() { + CLType::I32 => do_wrapping_addition::(cl_value, to_add), + CLType::I64 => do_wrapping_addition::(cl_value, to_add), + CLType::U8 => do_wrapping_addition::(cl_value, to_add), + CLType::U32 => do_wrapping_addition::(cl_value, to_add), + CLType::U64 => do_wrapping_addition::(cl_value, to_add), + CLType::U128 => do_wrapping_addition::(cl_value, to_add), + CLType::U256 => do_wrapping_addition::(cl_value, to_add), + CLType::U512 => do_wrapping_addition::(cl_value, to_add), + other => { + let expected = format!("integral type compatible with {}", any::type_name::()); + let found = format!("{:?}", other); + Err(TypeMismatch::new(expected, found).into()) + } + } +} + +/// Attempts a wrapping addition of `to_add` to the value represented by `cl_value`. +fn do_wrapping_addition(cl_value: CLValue, to_add: Y) -> Result +where + X: WrappingAdd + CLTyped + ToBytes + FromBytes + Copy + 'static, + Y: AsPrimitive, +{ + let x: X = cl_value.into_t()?; + let result = x.wrapping_add(&(to_add.as_())); + Ok(StoredValue::CLValue(CLValue::from_t(result)?)) +} + +impl Transform { + pub fn apply(self, stored_value: StoredValue) -> Result { + match self { + Transform::Identity => Ok(stored_value), + Transform::Write(new_value) => Ok(new_value), + Transform::AddInt32(to_add) => wrapping_addition(stored_value, to_add), + Transform::AddUInt64(to_add) => wrapping_addition(stored_value, to_add), + Transform::AddUInt128(to_add) => wrapping_addition(stored_value, to_add), + Transform::AddUInt256(to_add) => wrapping_addition(stored_value, to_add), + Transform::AddUInt512(to_add) => wrapping_addition(stored_value, to_add), + Transform::AddKeys(mut keys) => match stored_value { + StoredValue::Contract(mut contract) => { + contract.named_keys_append(&mut keys); + Ok(StoredValue::Contract(contract)) + } + StoredValue::Account(mut account) => { + account.named_keys_append(&mut keys); + Ok(StoredValue::Account(account)) + } + StoredValue::CLValue(cl_value) => { + let expected = "Contract or Account".to_string(); + let found = format!("{:?}", cl_value.cl_type()); + Err(TypeMismatch::new(expected, found).into()) + } + StoredValue::ContractPackage(_) => { + let expected = "Contract or Account".to_string(); + let found = "ContractPackage".to_string(); + Err(TypeMismatch::new(expected, found).into()) + } + StoredValue::ContractWasm(_) => { + let expected = "Contract or Account".to_string(); + let found = "ContractWasm".to_string(); + Err(TypeMismatch::new(expected, found).into()) + } + }, + Transform::Failure(error) => Err(error), + } + } +} + +/// Combines numeric `Transform`s into a single `Transform`. This is done by unwrapping the +/// `Transform` to obtain the underlying value, performing the wrapping addition then wrapping up as +/// a `Transform` again. +fn wrapped_transform_addition(i: T, b: Transform, expected: &str) -> Transform +where + T: WrappingAdd + + AsPrimitive + + From + + From + + Into + + TryFrom, + i32: AsPrimitive, +{ + if let Transform::AddInt32(j) = b { + i.wrapping_add(&j.as_()).into() + } else if let Transform::AddUInt64(j) = b { + i.wrapping_add(&j.into()).into() + } else { + match T::try_from(b) { + Err(b_type) => Transform::Failure( + TypeMismatch { + expected: String::from(expected), + found: b_type, + } + .into(), + ), + + Ok(j) => i.wrapping_add(&j).into(), + } + } +} + +impl Add for Transform { + type Output = Transform; + + fn add(self, other: Transform) -> Transform { + match (self, other) { + (a, Transform::Identity) => a, + (Transform::Identity, b) => b, + (a @ Transform::Failure(_), _) => a, + (_, b @ Transform::Failure(_)) => b, + (_, b @ Transform::Write(_)) => b, + (Transform::Write(v), b) => { + // second transform changes value being written + match b.apply(v) { + Err(error) => Transform::Failure(error), + Ok(new_value) => Transform::Write(new_value), + } + } + (Transform::AddInt32(i), b) => match b { + Transform::AddInt32(j) => Transform::AddInt32(i.wrapping_add(j)), + Transform::AddUInt64(j) => Transform::AddUInt64(j.wrapping_add(i as u64)), + Transform::AddUInt128(j) => Transform::AddUInt128(j.wrapping_add(&(i.as_()))), + Transform::AddUInt256(j) => Transform::AddUInt256(j.wrapping_add(&(i.as_()))), + Transform::AddUInt512(j) => Transform::AddUInt512(j.wrapping_add(&i.as_())), + other => Transform::Failure( + TypeMismatch::new("AddInt32".to_owned(), format!("{:?}", other)).into(), + ), + }, + (Transform::AddUInt64(i), b) => match b { + Transform::AddInt32(j) => Transform::AddInt32(j.wrapping_add(i as i32)), + Transform::AddUInt64(j) => Transform::AddUInt64(i.wrapping_add(j)), + Transform::AddUInt128(j) => Transform::AddUInt128(j.wrapping_add(&i.into())), + Transform::AddUInt256(j) => Transform::AddUInt256(j.wrapping_add(&i.into())), + Transform::AddUInt512(j) => Transform::AddUInt512(j.wrapping_add(&i.into())), + other => Transform::Failure( + TypeMismatch::new("AddUInt64".to_owned(), format!("{:?}", other)).into(), + ), + }, + (Transform::AddUInt128(i), b) => wrapped_transform_addition(i, b, "U128"), + (Transform::AddUInt256(i), b) => wrapped_transform_addition(i, b, "U256"), + (Transform::AddUInt512(i), b) => wrapped_transform_addition(i, b, "U512"), + (Transform::AddKeys(mut ks1), b) => match b { + Transform::AddKeys(mut ks2) => { + ks1.append(&mut ks2); + Transform::AddKeys(ks1) + } + other => Transform::Failure( + TypeMismatch::new("AddKeys".to_owned(), format!("{:?}", other)).into(), + ), + }, + } + } +} + +impl AddAssign for Transform { + fn add_assign(&mut self, other: Self) { + *self = self.clone() + other; + } +} + +impl Display for Transform { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl Default for Transform { + fn default() -> Self { + Transform::Identity + } +} + +#[cfg(any(feature = "gens", test))] +pub mod gens { + use proptest::{collection::vec, prelude::*}; + + use super::Transform; + use crate::contract_shared::stored_value::gens::stored_value_arb; + + pub fn transform_arb() -> impl Strategy { + prop_oneof![ + Just(Transform::Identity), + stored_value_arb().prop_map(Transform::Write), + any::().prop_map(Transform::AddInt32), + any::().prop_map(Transform::AddUInt64), + any::().prop_map(|u| Transform::AddUInt128(u.into())), + vec(any::(), 32).prop_map(|u| { + let mut buf: [u8; 32] = [0u8; 32]; + buf.copy_from_slice(&u); + Transform::AddUInt256(buf.into()) + }), + vec(any::(), 64).prop_map(|u| { + let mut buf: [u8; 64] = [0u8; 64]; + buf.copy_from_slice(&u); + Transform::AddUInt512(buf.into()) + }), + ] + } +} + +#[cfg(test)] +mod tests { + use num::{Bounded, Num}; + + use types::{account::AccountHash, AccessRights, ContractWasm, Key, URef, U128, U256, U512}; + + use super::*; + use crate::contract_shared::account::{Account, ActionThresholds, AssociatedKeys}; + use std::collections::BTreeMap; + + const ZERO_ARRAY: [u8; 32] = [0; 32]; + const ZERO_PUBLIC_KEY: AccountHash = AccountHash::new(ZERO_ARRAY); + const TEST_STR: &str = "a"; + const TEST_BOOL: bool = true; + + const ZERO_I32: i32 = 0; + const ONE_I32: i32 = 1; + const NEG_ONE_I32: i32 = -1; + const NEG_TWO_I32: i32 = -2; + const MIN_I32: i32 = i32::min_value(); + const MAX_I32: i32 = i32::max_value(); + + const ZERO_I64: i64 = 0; + const ONE_I64: i64 = 1; + const NEG_ONE_I64: i64 = -1; + const NEG_TWO_I64: i64 = -2; + const MIN_I64: i64 = i64::min_value(); + const MAX_I64: i64 = i64::max_value(); + + const ZERO_U8: u8 = 0; + const ONE_U8: u8 = 1; + const MAX_U8: u8 = u8::max_value(); + + const ZERO_U32: u32 = 0; + const ONE_U32: u32 = 1; + const MAX_U32: u32 = u32::max_value(); + + const ZERO_U64: u64 = 0; + const ONE_U64: u64 = 1; + const MAX_U64: u64 = u64::max_value(); + + const ZERO_U128: U128 = U128([0; 2]); + const ONE_U128: U128 = U128([1, 0]); + const MAX_U128: U128 = U128([MAX_U64; 2]); + + const ZERO_U256: U256 = U256([0; 4]); + const ONE_U256: U256 = U256([1, 0, 0, 0]); + const MAX_U256: U256 = U256([MAX_U64; 4]); + + const ZERO_U512: U512 = U512([0; 8]); + const ONE_U512: U512 = U512([1, 0, 0, 0, 0, 0, 0, 0]); + const MAX_U512: U512 = U512([MAX_U64; 8]); + + #[test] + fn i32_overflow() { + let max = std::i32::MAX; + let min = std::i32::MIN; + + let max_value = StoredValue::CLValue(CLValue::from_t(max).unwrap()); + let min_value = StoredValue::CLValue(CLValue::from_t(min).unwrap()); + + let apply_overflow = Transform::AddInt32(1).apply(max_value.clone()); + let apply_underflow = Transform::AddInt32(-1).apply(min_value.clone()); + + let transform_overflow = Transform::AddInt32(max) + Transform::AddInt32(1); + let transform_underflow = Transform::AddInt32(min) + Transform::AddInt32(-1); + + assert_eq!(apply_overflow.expect("Unexpected overflow"), min_value); + assert_eq!(apply_underflow.expect("Unexpected underflow"), max_value); + + assert_eq!(transform_overflow, min.into()); + assert_eq!(transform_underflow, max.into()); + } + + fn uint_overflow_test() + where + T: Num + Bounded + CLTyped + ToBytes + Into + Copy, + { + let max = T::max_value(); + let min = T::min_value(); + let one = T::one(); + let zero = T::zero(); + + let max_value = StoredValue::CLValue(CLValue::from_t(max).unwrap()); + let min_value = StoredValue::CLValue(CLValue::from_t(min).unwrap()); + let zero_value = StoredValue::CLValue(CLValue::from_t(zero).unwrap()); + + let max_transform: Transform = max.into(); + let min_transform: Transform = min.into(); + + let one_transform: Transform = one.into(); + + let apply_overflow = Transform::AddInt32(1).apply(max_value.clone()); + + let apply_overflow_uint = one_transform.clone().apply(max_value.clone()); + let apply_underflow = Transform::AddInt32(-1).apply(min_value); + + let transform_overflow = max_transform.clone() + Transform::AddInt32(1); + let transform_overflow_uint = max_transform + one_transform; + let transform_underflow = min_transform + Transform::AddInt32(-1); + + assert_eq!(apply_overflow, Ok(zero_value.clone())); + assert_eq!(apply_overflow_uint, Ok(zero_value)); + assert_eq!(apply_underflow, Ok(max_value)); + + assert_eq!(transform_overflow, zero.into()); + assert_eq!(transform_overflow_uint, zero.into()); + assert_eq!(transform_underflow, max.into()); + } + + #[test] + fn u128_overflow() { + uint_overflow_test::(); + } + + #[test] + fn u256_overflow() { + uint_overflow_test::(); + } + + #[test] + fn u512_overflow() { + uint_overflow_test::(); + } + + #[test] + fn addition_between_mismatched_types_should_fail() { + fn assert_yields_type_mismatch_error(stored_value: StoredValue) { + match wrapping_addition(stored_value, ZERO_I32) { + Err(Error::TypeMismatch(_)) => (), + _ => panic!("wrapping addition should yield TypeMismatch error"), + }; + } + + let contract = StoredValue::ContractWasm(ContractWasm::new(vec![])); + assert_yields_type_mismatch_error(contract); + + let uref = URef::new(ZERO_ARRAY, AccessRights::READ); + let account = StoredValue::Account(Account::new( + ZERO_PUBLIC_KEY, + NamedKeys::new(), + uref, + AssociatedKeys::default(), + ActionThresholds::default(), + )); + assert_yields_type_mismatch_error(account); + + let cl_bool = + StoredValue::CLValue(CLValue::from_t(TEST_BOOL).expect("should create CLValue")); + assert_yields_type_mismatch_error(cl_bool); + + let cl_unit = StoredValue::CLValue(CLValue::from_t(()).expect("should create CLValue")); + assert_yields_type_mismatch_error(cl_unit); + + let cl_string = + StoredValue::CLValue(CLValue::from_t(TEST_STR).expect("should create CLValue")); + assert_yields_type_mismatch_error(cl_string); + + let cl_key = StoredValue::CLValue( + CLValue::from_t(Key::Hash(ZERO_ARRAY)).expect("should create CLValue"), + ); + assert_yields_type_mismatch_error(cl_key); + + let cl_uref = StoredValue::CLValue(CLValue::from_t(uref).expect("should create CLValue")); + assert_yields_type_mismatch_error(cl_uref); + + let cl_option = + StoredValue::CLValue(CLValue::from_t(Some(ZERO_U8)).expect("should create CLValue")); + assert_yields_type_mismatch_error(cl_option); + + let cl_list = + StoredValue::CLValue(CLValue::from_t(vec![ZERO_U8]).expect("should create CLValue")); + assert_yields_type_mismatch_error(cl_list); + + let cl_fixed_list = + StoredValue::CLValue(CLValue::from_t([ZERO_U8]).expect("should create CLValue")); + assert_yields_type_mismatch_error(cl_fixed_list); + + let cl_result: Result<(), u8> = Err(ZERO_U8); + let cl_result = + StoredValue::CLValue(CLValue::from_t(cl_result).expect("should create CLValue")); + assert_yields_type_mismatch_error(cl_result); + + let cl_map = StoredValue::CLValue( + CLValue::from_t(BTreeMap::::new()).expect("should create CLValue"), + ); + assert_yields_type_mismatch_error(cl_map); + + let cl_tuple1 = + StoredValue::CLValue(CLValue::from_t((ZERO_U8,)).expect("should create CLValue")); + assert_yields_type_mismatch_error(cl_tuple1); + + let cl_tuple2 = StoredValue::CLValue( + CLValue::from_t((ZERO_U8, ZERO_U8)).expect("should create CLValue"), + ); + assert_yields_type_mismatch_error(cl_tuple2); + + let cl_tuple3 = StoredValue::CLValue( + CLValue::from_t((ZERO_U8, ZERO_U8, ZERO_U8)).expect("should create CLValue"), + ); + assert_yields_type_mismatch_error(cl_tuple3); + } + + #[test] + #[allow(clippy::cognitive_complexity)] + fn wrapping_addition_should_succeed() { + fn add(current_value: X, to_add: Y) -> X + where + X: CLTyped + ToBytes + FromBytes + PartialEq + fmt::Debug, + Y: AsPrimitive + + AsPrimitive + + AsPrimitive + + AsPrimitive + + AsPrimitive + + AsPrimitive + + AsPrimitive + + AsPrimitive, + { + let current = StoredValue::CLValue( + CLValue::from_t(current_value).expect("should create CLValue"), + ); + let result = + wrapping_addition(current, to_add).expect("wrapping addition should succeed"); + CLValue::try_from(result) + .expect("should be CLValue") + .into_t() + .expect("should parse to X") + } + + // Adding to i32 + assert_eq!(ONE_I32, add(ZERO_I32, ONE_I32)); + assert_eq!(MIN_I32, add(MAX_I32, ONE_I32)); + assert_eq!(NEG_TWO_I32, add(MAX_I32, MAX_I32)); + assert_eq!(ZERO_I32, add(ONE_I32, NEG_ONE_I32)); + assert_eq!(NEG_ONE_I32, add(ZERO_I32, NEG_ONE_I32)); + assert_eq!(MAX_I32, add(NEG_ONE_I32, MIN_I32)); + + assert_eq!(ONE_I32, add(ZERO_I32, ONE_U64)); + assert_eq!(MIN_I32, add(MAX_I32, ONE_U64)); + assert_eq!(NEG_TWO_I32, add(MAX_I32, MAX_I32 as u64)); + + assert_eq!(ONE_I32, add(ZERO_I32, ONE_U128)); + assert_eq!(MIN_I32, add(MAX_I32, ONE_U128)); + assert_eq!(NEG_TWO_I32, add(MAX_I32, U128::from(MAX_I32))); + + assert_eq!(ONE_I32, add(ZERO_I32, ONE_U256)); + assert_eq!(MIN_I32, add(MAX_I32, ONE_U256)); + assert_eq!(NEG_TWO_I32, add(MAX_I32, U256::from(MAX_I32))); + + assert_eq!(ONE_I32, add(ZERO_I32, ONE_U512)); + assert_eq!(MIN_I32, add(MAX_I32, ONE_U512)); + assert_eq!(NEG_TWO_I32, add(MAX_I32, U512::from(MAX_I32))); + + // Adding to i64 + assert_eq!(ONE_I64, add(ZERO_I64, ONE_I32)); + assert_eq!(MIN_I64, add(MAX_I64, ONE_I32)); + assert_eq!(ZERO_I64, add(ONE_I64, NEG_ONE_I32)); + assert_eq!(NEG_ONE_I64, add(ZERO_I64, NEG_ONE_I32)); + assert_eq!(MAX_I64, add(MIN_I64, NEG_ONE_I32)); + + assert_eq!(ONE_I64, add(ZERO_I64, ONE_U64)); + assert_eq!(MIN_I64, add(MAX_I64, ONE_U64)); + assert_eq!(NEG_TWO_I64, add(MAX_I64, MAX_I64 as u64)); + + assert_eq!(ONE_I64, add(ZERO_I64, ONE_U128)); + assert_eq!(MIN_I64, add(MAX_I64, ONE_U128)); + assert_eq!(NEG_TWO_I64, add(MAX_I64, U128::from(MAX_I64))); + + assert_eq!(ONE_I64, add(ZERO_I64, ONE_U256)); + assert_eq!(MIN_I64, add(MAX_I64, ONE_U256)); + assert_eq!(NEG_TWO_I64, add(MAX_I64, U256::from(MAX_I64))); + + assert_eq!(ONE_I64, add(ZERO_I64, ONE_U512)); + assert_eq!(MIN_I64, add(MAX_I64, ONE_U512)); + assert_eq!(NEG_TWO_I64, add(MAX_I64, U512::from(MAX_I64))); + + // Adding to u8 + assert_eq!(ONE_U8, add(ZERO_U8, ONE_I32)); + assert_eq!(ZERO_U8, add(MAX_U8, ONE_I32)); + assert_eq!(MAX_U8, add(MAX_U8, 256_i32)); + assert_eq!(ZERO_U8, add(MAX_U8, 257_i32)); + assert_eq!(ZERO_U8, add(ONE_U8, NEG_ONE_I32)); + assert_eq!(MAX_U8, add(ZERO_U8, NEG_ONE_I32)); + assert_eq!(ZERO_U8, add(ZERO_U8, -256_i32)); + assert_eq!(MAX_U8, add(ZERO_U8, -257_i32)); + assert_eq!(MAX_U8, add(ZERO_U8, MAX_I32)); + assert_eq!(ZERO_U8, add(ZERO_U8, MIN_I32)); + + assert_eq!(ONE_U8, add(ZERO_U8, ONE_U64)); + assert_eq!(ZERO_U8, add(MAX_U8, ONE_U64)); + assert_eq!(ONE_U8, add(ZERO_U8, u64::from(MAX_U8) + 2)); + assert_eq!(MAX_U8, add(ZERO_U8, MAX_U64)); + + assert_eq!(ONE_U8, add(ZERO_U8, ONE_U128)); + assert_eq!(ZERO_U8, add(MAX_U8, ONE_U128)); + assert_eq!(ONE_U8, add(ZERO_U8, U128::from(MAX_U8) + 2)); + assert_eq!(MAX_U8, add(ZERO_U8, MAX_U128)); + + assert_eq!(ONE_U8, add(ZERO_U8, ONE_U256)); + assert_eq!(ZERO_U8, add(MAX_U8, ONE_U256)); + assert_eq!(ONE_U8, add(ZERO_U8, U256::from(MAX_U8) + 2)); + assert_eq!(MAX_U8, add(ZERO_U8, MAX_U256)); + + assert_eq!(ONE_U8, add(ZERO_U8, ONE_U512)); + assert_eq!(ZERO_U8, add(MAX_U8, ONE_U512)); + assert_eq!(ONE_U8, add(ZERO_U8, U512::from(MAX_U8) + 2)); + assert_eq!(MAX_U8, add(ZERO_U8, MAX_U512)); + + // Adding to u32 + assert_eq!(ONE_U32, add(ZERO_U32, ONE_I32)); + assert_eq!(ZERO_U32, add(MAX_U32, ONE_I32)); + assert_eq!(ZERO_U32, add(ONE_U32, NEG_ONE_I32)); + assert_eq!(MAX_U32, add(ZERO_U32, NEG_ONE_I32)); + assert_eq!(MAX_I32 as u32 + 1, add(ZERO_U32, MIN_I32)); + + assert_eq!(ONE_U32, add(ZERO_U32, ONE_U64)); + assert_eq!(ZERO_U32, add(MAX_U32, ONE_U64)); + assert_eq!(ONE_U32, add(ZERO_U32, u64::from(MAX_U32) + 2)); + assert_eq!(MAX_U32, add(ZERO_U32, MAX_U64)); + + assert_eq!(ONE_U32, add(ZERO_U32, ONE_U128)); + assert_eq!(ZERO_U32, add(MAX_U32, ONE_U128)); + assert_eq!(ONE_U32, add(ZERO_U32, U128::from(MAX_U32) + 2)); + assert_eq!(MAX_U32, add(ZERO_U32, MAX_U128)); + + assert_eq!(ONE_U32, add(ZERO_U32, ONE_U256)); + assert_eq!(ZERO_U32, add(MAX_U32, ONE_U256)); + assert_eq!(ONE_U32, add(ZERO_U32, U256::from(MAX_U32) + 2)); + assert_eq!(MAX_U32, add(ZERO_U32, MAX_U256)); + + assert_eq!(ONE_U32, add(ZERO_U32, ONE_U512)); + assert_eq!(ZERO_U32, add(MAX_U32, ONE_U512)); + assert_eq!(ONE_U32, add(ZERO_U32, U512::from(MAX_U32) + 2)); + assert_eq!(MAX_U32, add(ZERO_U32, MAX_U512)); + + // Adding to u64 + assert_eq!(ONE_U64, add(ZERO_U64, ONE_I32)); + assert_eq!(ZERO_U64, add(MAX_U64, ONE_I32)); + assert_eq!(ZERO_U64, add(ONE_U64, NEG_ONE_I32)); + assert_eq!(MAX_U64, add(ZERO_U64, NEG_ONE_I32)); + + assert_eq!(ONE_U64, add(ZERO_U64, ONE_U64)); + assert_eq!(ZERO_U64, add(MAX_U64, ONE_U64)); + assert_eq!(MAX_U64 - 1, add(MAX_U64, MAX_U64)); + + assert_eq!(ONE_U64, add(ZERO_U64, ONE_U128)); + assert_eq!(ZERO_U64, add(MAX_U64, ONE_U128)); + assert_eq!(ONE_U64, add(ZERO_U64, U128::from(MAX_U64) + 2)); + assert_eq!(MAX_U64, add(ZERO_U64, MAX_U128)); + + assert_eq!(ONE_U64, add(ZERO_U64, ONE_U256)); + assert_eq!(ZERO_U64, add(MAX_U64, ONE_U256)); + assert_eq!(ONE_U64, add(ZERO_U64, U256::from(MAX_U64) + 2)); + assert_eq!(MAX_U64, add(ZERO_U64, MAX_U256)); + + assert_eq!(ONE_U64, add(ZERO_U64, ONE_U512)); + assert_eq!(ZERO_U64, add(MAX_U64, ONE_U512)); + assert_eq!(ONE_U64, add(ZERO_U64, U512::from(MAX_U64) + 2)); + assert_eq!(MAX_U64, add(ZERO_U64, MAX_U512)); + + // Adding to U128 + assert_eq!(ONE_U128, add(ZERO_U128, ONE_I32)); + assert_eq!(ZERO_U128, add(MAX_U128, ONE_I32)); + assert_eq!(ZERO_U128, add(ONE_U128, NEG_ONE_I32)); + assert_eq!(MAX_U128, add(ZERO_U128, NEG_ONE_I32)); + + assert_eq!(ONE_U128, add(ZERO_U128, ONE_U64)); + assert_eq!(ZERO_U128, add(MAX_U128, ONE_U64)); + + assert_eq!(ONE_U128, add(ZERO_U128, ONE_U128)); + assert_eq!(ZERO_U128, add(MAX_U128, ONE_U128)); + assert_eq!(MAX_U128 - 1, add(MAX_U128, MAX_U128)); + + assert_eq!(ONE_U128, add(ZERO_U128, ONE_U256)); + assert_eq!(ZERO_U128, add(MAX_U128, ONE_U256)); + assert_eq!( + ONE_U128, + add( + ZERO_U128, + U256::from_dec_str(&MAX_U128.to_string()).unwrap() + 2, + ) + ); + assert_eq!(MAX_U128, add(ZERO_U128, MAX_U256)); + + assert_eq!(ONE_U128, add(ZERO_U128, ONE_U512)); + assert_eq!(ZERO_U128, add(MAX_U128, ONE_U512)); + assert_eq!( + ONE_U128, + add( + ZERO_U128, + U512::from_dec_str(&MAX_U128.to_string()).unwrap() + 2, + ) + ); + assert_eq!(MAX_U128, add(ZERO_U128, MAX_U512)); + + // Adding to U256 + assert_eq!(ONE_U256, add(ZERO_U256, ONE_I32)); + assert_eq!(ZERO_U256, add(MAX_U256, ONE_I32)); + assert_eq!(ZERO_U256, add(ONE_U256, NEG_ONE_I32)); + assert_eq!(MAX_U256, add(ZERO_U256, NEG_ONE_I32)); + + assert_eq!(ONE_U256, add(ZERO_U256, ONE_U64)); + assert_eq!(ZERO_U256, add(MAX_U256, ONE_U64)); + + assert_eq!(ONE_U256, add(ZERO_U256, ONE_U128)); + assert_eq!(ZERO_U256, add(MAX_U256, ONE_U128)); + + assert_eq!(ONE_U256, add(ZERO_U256, ONE_U256)); + assert_eq!(ZERO_U256, add(MAX_U256, ONE_U256)); + assert_eq!(MAX_U256 - 1, add(MAX_U256, MAX_U256)); + + assert_eq!(ONE_U256, add(ZERO_U256, ONE_U512)); + assert_eq!(ZERO_U256, add(MAX_U256, ONE_U512)); + assert_eq!( + ONE_U256, + add( + ZERO_U256, + U512::from_dec_str(&MAX_U256.to_string()).unwrap() + 2, + ) + ); + assert_eq!(MAX_U256, add(ZERO_U256, MAX_U512)); + + // Adding to U512 + assert_eq!(ONE_U512, add(ZERO_U512, ONE_I32)); + assert_eq!(ZERO_U512, add(MAX_U512, ONE_I32)); + assert_eq!(ZERO_U512, add(ONE_U512, NEG_ONE_I32)); + assert_eq!(MAX_U512, add(ZERO_U512, NEG_ONE_I32)); + + assert_eq!(ONE_U512, add(ZERO_U512, ONE_U64)); + assert_eq!(ZERO_U512, add(MAX_U512, ONE_U64)); + + assert_eq!(ONE_U512, add(ZERO_U512, ONE_U128)); + assert_eq!(ZERO_U512, add(MAX_U512, ONE_U128)); + + assert_eq!(ONE_U512, add(ZERO_U512, ONE_U256)); + assert_eq!(ZERO_U512, add(MAX_U512, ONE_U256)); + + assert_eq!(ONE_U512, add(ZERO_U512, ONE_U512)); + assert_eq!(ZERO_U512, add(MAX_U512, ONE_U512)); + assert_eq!(MAX_U512 - 1, add(MAX_U512, MAX_U512)); + } +} diff --git a/node/src/contract_shared/type_mismatch.rs b/node/src/contract_shared/type_mismatch.rs new file mode 100644 index 0000000000..21379689a6 --- /dev/null +++ b/node/src/contract_shared/type_mismatch.rs @@ -0,0 +1,23 @@ +use std::fmt; + +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct TypeMismatch { + pub expected: String, + pub found: String, +} + +impl fmt::Display for TypeMismatch { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + f, + "Type mismatch. Expected {} but found {}.", + self.expected, self.found + ) + } +} + +impl TypeMismatch { + pub fn new(expected: String, found: String) -> TypeMismatch { + TypeMismatch { expected, found } + } +} diff --git a/node/src/contract_shared/utils.rs b/node/src/contract_shared/utils.rs new file mode 100644 index 0000000000..79540b0be0 --- /dev/null +++ b/node/src/contract_shared/utils.rs @@ -0,0 +1,79 @@ +use serde::Serialize; + +/// serializes value to json; +/// pretty_print: false = inline +/// pretty_print: true = pretty printed / multiline +pub fn jsonify(value: T, pretty_print: bool) -> String +where + T: Serialize, +{ + let fj = if pretty_print { + serde_json::to_string_pretty + } else { + serde_json::to_string + }; + + match fj(&value) { + Ok(json) => json, + Err(_) => r#"{"error": "encountered error serializing value"}"#.to_owned(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Debug, Deserialize, Eq, Serialize)] + struct SerMock { + foo: String, + bar: u32, + } + + impl PartialEq for SerMock { + fn eq(&self, other: &SerMock) -> bool { + self.foo.eq(&other.foo) && self.bar == other.bar + } + } + + #[test] + fn should_ser_to_json() { + let sermock = SerMock { + foo: "foo".to_string(), + bar: 1, + }; + + let json = jsonify(sermock, false); + + assert_eq!(json, r#"{"foo":"foo","bar":1}"#, "json expected to match"); + } + + #[test] + fn should_ser_to_pretty_json() { + let sermock = SerMock { + foo: "foo".to_string(), + bar: 1, + }; + + let json = jsonify(sermock, true); + + assert!(json.contains('\n'), "json expected to be multiline"); + } + + #[test] + fn should_deser_from_json() { + let sermock = SerMock { + foo: "foo".to_string(), + bar: 1, + }; + + let json = jsonify(&sermock, false); + + let sermock_clone: SerMock = serde_json::from_str(&json).expect("should deser"); + + assert!( + sermock.eq(&sermock_clone), + "instances should contain the same data" + ); + } +} diff --git a/node/src/contract_shared/wasm.rs b/node/src/contract_shared/wasm.rs new file mode 100644 index 0000000000..8ad57d9e44 --- /dev/null +++ b/node/src/contract_shared/wasm.rs @@ -0,0 +1,27 @@ +use parity_wasm::elements::Module; + +use crate::contract_shared::wasm_prep::{PreprocessingError, Preprocessor}; + +static DO_NOTHING: &str = r#" + (module + (type (;0;) (func)) + (func $call (type 0)) + (table (;0;) 1 1 funcref) + (memory (;0;) 16) + (global (;0;) (mut i32) (i32.const 1048576)) + (global (;1;) i32 (i32.const 1048576)) + (global (;2;) i32 (i32.const 1048576)) + (export "memory" (memory 0)) + (export "call" (func $call)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2))) + "#; + +pub fn do_nothing_bytes() -> Vec { + wabt::wat2wasm(DO_NOTHING).expect("failed to parse wat") +} + +pub fn do_nothing_module(preprocessor: &Preprocessor) -> Result { + let do_nothing_bytes = do_nothing_bytes(); + preprocessor.preprocess(&do_nothing_bytes) +} diff --git a/node/src/contract_shared/wasm_costs.rs b/node/src/contract_shared/wasm_costs.rs new file mode 100644 index 0000000000..d6163d33b9 --- /dev/null +++ b/node/src/contract_shared/wasm_costs.rs @@ -0,0 +1,193 @@ +use std::collections::BTreeMap; + +use pwasm_utils::rules::{InstructionType, Metering, Set}; + +use types::bytesrepr::{self, FromBytes, ToBytes, U32_SERIALIZED_LENGTH}; + +const NUM_FIELDS: usize = 10; +pub const WASM_COSTS_SERIALIZED_LENGTH: usize = NUM_FIELDS * U32_SERIALIZED_LENGTH; + +// Taken (partially) from parity-ethereum +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct WasmCosts { + /// Default opcode cost + pub regular: u32, + /// Div operations multiplier. + pub div: u32, + /// Mul operations multiplier. + pub mul: u32, + /// Memory (load/store) operations multiplier. + pub mem: u32, + /// Memory stipend. Amount of free memory (in 64kb pages) each contract can + /// use for stack. + pub initial_mem: u32, + /// Grow memory cost, per page (64kb) + pub grow_mem: u32, + /// Memory copy cost, per byte + pub memcpy: u32, + /// Max stack height (native WebAssembly stack limiter) + pub max_stack_height: u32, + /// Cost of wasm opcode is calculated as TABLE_ENTRY_COST * `opcodes_mul` / + /// `opcodes_div` + pub opcodes_mul: u32, + /// Cost of wasm opcode is calculated as TABLE_ENTRY_COST * `opcodes_mul` / + /// `opcodes_div` + pub opcodes_div: u32, +} + +impl WasmCosts { + pub(crate) fn to_set(&self) -> Set { + let meterings = { + let mut tmp = BTreeMap::new(); + tmp.insert(InstructionType::Load, Metering::Fixed(self.mem)); + tmp.insert(InstructionType::Store, Metering::Fixed(self.mem)); + tmp.insert(InstructionType::Div, Metering::Fixed(self.div)); + tmp.insert(InstructionType::Mul, Metering::Fixed(self.mul)); + tmp + }; + Set::new(self.regular, meterings) + .with_grow_cost(self.grow_mem) + .with_forbidden_floats() + } +} + +impl ToBytes for WasmCosts { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut ret = bytesrepr::unchecked_allocate_buffer(self); + ret.append(&mut self.regular.to_bytes()?); + ret.append(&mut self.div.to_bytes()?); + ret.append(&mut self.mul.to_bytes()?); + ret.append(&mut self.mem.to_bytes()?); + ret.append(&mut self.initial_mem.to_bytes()?); + ret.append(&mut self.grow_mem.to_bytes()?); + ret.append(&mut self.memcpy.to_bytes()?); + ret.append(&mut self.max_stack_height.to_bytes()?); + ret.append(&mut self.opcodes_mul.to_bytes()?); + ret.append(&mut self.opcodes_div.to_bytes()?); + Ok(ret) + } + + fn serialized_length(&self) -> usize { + WASM_COSTS_SERIALIZED_LENGTH + } +} + +impl FromBytes for WasmCosts { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (regular, rem): (u32, &[u8]) = FromBytes::from_bytes(bytes)?; + let (div, rem): (u32, &[u8]) = FromBytes::from_bytes(rem)?; + let (mul, rem): (u32, &[u8]) = FromBytes::from_bytes(rem)?; + let (mem, rem): (u32, &[u8]) = FromBytes::from_bytes(rem)?; + let (initial_mem, rem): (u32, &[u8]) = FromBytes::from_bytes(rem)?; + let (grow_mem, rem): (u32, &[u8]) = FromBytes::from_bytes(rem)?; + let (memcpy, rem): (u32, &[u8]) = FromBytes::from_bytes(rem)?; + let (max_stack_height, rem): (u32, &[u8]) = FromBytes::from_bytes(rem)?; + let (opcodes_mul, rem): (u32, &[u8]) = FromBytes::from_bytes(rem)?; + let (opcodes_div, rem): (u32, &[u8]) = FromBytes::from_bytes(rem)?; + let wasm_costs = WasmCosts { + regular, + div, + mul, + mem, + initial_mem, + grow_mem, + memcpy, + max_stack_height, + opcodes_mul, + opcodes_div, + }; + Ok((wasm_costs, rem)) + } +} + +#[cfg(any(feature = "gens", test))] +pub mod gens { + use proptest::{num, prop_compose}; + + use crate::contract_shared::wasm_costs::WasmCosts; + + prop_compose! { + pub fn wasm_costs_arb()( + regular in num::u32::ANY, + div in num::u32::ANY, + mul in num::u32::ANY, + mem in num::u32::ANY, + initial_mem in num::u32::ANY, + grow_mem in num::u32::ANY, + memcpy in num::u32::ANY, + max_stack_height in num::u32::ANY, + opcodes_mul in num::u32::ANY, + opcodes_div in num::u32::ANY, + ) -> WasmCosts { + WasmCosts { + regular, + div, + mul, + mem, + initial_mem, + grow_mem, + memcpy, + max_stack_height, + opcodes_mul, + opcodes_div, + } + } + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use types::bytesrepr; + + use super::gens; + use crate::contract_shared::wasm_costs::WasmCosts; + + fn wasm_costs_mock() -> WasmCosts { + WasmCosts { + regular: 1, + div: 16, + mul: 4, + mem: 2, + initial_mem: 4096, + grow_mem: 8192, + memcpy: 1, + max_stack_height: 64 * 1024, + opcodes_mul: 3, + opcodes_div: 8, + } + } + + fn wasm_costs_free() -> WasmCosts { + WasmCosts { + regular: 0, + div: 0, + mul: 0, + mem: 0, + initial_mem: 4096, + grow_mem: 8192, + memcpy: 0, + max_stack_height: 64 * 1024, + opcodes_mul: 1, + opcodes_div: 1, + } + } + + #[test] + fn should_serialize_and_deserialize() { + let mock = wasm_costs_mock(); + let free = wasm_costs_free(); + bytesrepr::test_serialization_roundtrip(&mock); + bytesrepr::test_serialization_roundtrip(&free); + } + + proptest! { + #[test] + fn should_serialize_and_deserialize_with_arbitrary_values( + wasm_costs in gens::wasm_costs_arb() + ) { + bytesrepr::test_serialization_roundtrip(&wasm_costs); + } + } +} diff --git a/node/src/contract_shared/wasm_prep.rs b/node/src/contract_shared/wasm_prep.rs new file mode 100644 index 0000000000..74fab10f1f --- /dev/null +++ b/node/src/contract_shared/wasm_prep.rs @@ -0,0 +1,62 @@ +use std::fmt::{self, Display, Formatter}; + +use parity_wasm::elements::{self, Module}; +use pwasm_utils::{self, stack_height}; + +use crate::contract_shared::wasm_costs::WasmCosts; + +//NOTE: size of Wasm memory page is 64 KiB +pub const MEM_PAGES: u32 = 64; + +#[derive(Debug, Clone)] +pub enum PreprocessingError { + Deserialize(String), + OperationForbiddenByGasRules, + StackLimiter, +} + +impl From for PreprocessingError { + fn from(error: elements::Error) -> Self { + PreprocessingError::Deserialize(error.to_string()) + } +} + +impl Display for PreprocessingError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + PreprocessingError::Deserialize(error) => write!(f, "Deserialization error: {}", error), + PreprocessingError::OperationForbiddenByGasRules => write!(f, "Encountered operation forbidden by gas rules. Consult instruction -> metering config map"), + PreprocessingError::StackLimiter => write!(f, "Stack limiter error"), + } + } +} + +pub struct Preprocessor { + wasm_costs: WasmCosts, + // Number of memory pages. + mem_pages: u32, +} + +impl Preprocessor { + pub fn new(wasm_costs: WasmCosts) -> Self { + Self { + wasm_costs, + mem_pages: MEM_PAGES, + } + } + + pub fn preprocess(&self, module_bytes: &[u8]) -> Result { + let module = deserialize(module_bytes)?; + let module = pwasm_utils::externalize_mem(module, None, self.mem_pages); + let module = pwasm_utils::inject_gas_counter(module, &self.wasm_costs.to_set()) + .map_err(|_| PreprocessingError::OperationForbiddenByGasRules)?; + let module = stack_height::inject_limiter(module, self.wasm_costs.max_stack_height) + .map_err(|_| PreprocessingError::StackLimiter)?; + Ok(module) + } +} + +// Returns a parity Module from bytes without making modifications or limits +pub fn deserialize(module_bytes: &[u8]) -> Result { + parity_wasm::deserialize_buffer::(module_bytes).map_err(Into::into) +} diff --git a/node/src/contract_storage.rs b/node/src/contract_storage.rs new file mode 100644 index 0000000000..7dfb96195b --- /dev/null +++ b/node/src/contract_storage.rs @@ -0,0 +1,28 @@ +#![allow(missing_docs)] + +// modules +pub mod error; +pub mod global_state; +pub mod protocol_data; +pub mod protocol_data_store; +pub mod store; +pub mod transaction_source; +pub mod trie; +pub mod trie_store; + +#[cfg(test)] +use lazy_static::lazy_static; + +pub(crate) const GAUGE_METRIC_KEY: &str = "gauge"; +const MAX_DBS: u32 = 2; + +#[cfg(test)] +lazy_static! { + // 50 MiB = 52428800 bytes + // page size on x86_64 linux = 4096 bytes + // 52428800 / 4096 = 12800 + static ref TEST_MAP_SIZE: usize = { + let page_size = crate::contract_shared::page_size::get_page_size().unwrap(); + page_size * 12800 + }; +} diff --git a/node/src/contract_storage/error/in_memory.rs b/node/src/contract_storage/error/in_memory.rs new file mode 100644 index 0000000000..e3bedb3ded --- /dev/null +++ b/node/src/contract_storage/error/in_memory.rs @@ -0,0 +1,26 @@ +use std::sync; + +use failure::Fail; + +use types::bytesrepr; + +#[derive(Debug, Fail, PartialEq, Eq)] +pub enum Error { + #[fail(display = "{}", _0)] + BytesRepr(#[fail(cause)] bytesrepr::Error), + + #[fail(display = "Another thread panicked while holding a lock")] + Poison, +} + +impl From for Error { + fn from(error: bytesrepr::Error) -> Self { + Error::BytesRepr(error) + } +} + +impl From> for Error { + fn from(_error: sync::PoisonError) -> Self { + Error::Poison + } +} diff --git a/node/src/contract_storage/error/lmdb.rs b/node/src/contract_storage/error/lmdb.rs new file mode 100644 index 0000000000..da06c4faef --- /dev/null +++ b/node/src/contract_storage/error/lmdb.rs @@ -0,0 +1,49 @@ +use std::sync; + +use failure::Fail; +use lmdb as lmdb_external; + +use types::bytesrepr; + +use super::in_memory; + +#[derive(Debug, Clone, Fail, PartialEq, Eq)] +pub enum Error { + #[fail(display = "{}", _0)] + Lmdb(#[fail(cause)] lmdb_external::Error), + + #[fail(display = "{}", _0)] + BytesRepr(#[fail(cause)] bytesrepr::Error), + + #[fail(display = "Another thread panicked while holding a lock")] + Poison, +} + +impl wasmi::HostError for Error {} + +impl From for Error { + fn from(error: lmdb_external::Error) -> Self { + Error::Lmdb(error) + } +} + +impl From for Error { + fn from(error: bytesrepr::Error) -> Self { + Error::BytesRepr(error) + } +} + +impl From> for Error { + fn from(_error: sync::PoisonError) -> Self { + Error::Poison + } +} + +impl From for Error { + fn from(error: in_memory::Error) -> Self { + match error { + in_memory::Error::BytesRepr(error) => Error::BytesRepr(error), + in_memory::Error::Poison => Error::Poison, + } + } +} diff --git a/node/src/contract_storage/error/mod.rs b/node/src/contract_storage/error/mod.rs new file mode 100644 index 0000000000..264cb476bb --- /dev/null +++ b/node/src/contract_storage/error/mod.rs @@ -0,0 +1,4 @@ +pub mod in_memory; +pub mod lmdb; + +pub use self::lmdb::Error; diff --git a/node/src/contract_storage/global_state/in_memory.rs b/node/src/contract_storage/global_state/in_memory.rs new file mode 100644 index 0000000000..5e4e4d9783 --- /dev/null +++ b/node/src/contract_storage/global_state/in_memory.rs @@ -0,0 +1,356 @@ +use std::{ops::Deref, sync::Arc}; + +use crate::contract_shared::{ + additive_map::AdditiveMap, + newtypes::{Blake2bHash, CorrelationId}, + stored_value::StoredValue, + transform::Transform, +}; +use types::{Key, ProtocolVersion}; + +use crate::contract_storage::{ + error::{self, in_memory}, + global_state::{commit, CommitResult, StateProvider, StateReader}, + protocol_data::ProtocolData, + protocol_data_store::in_memory::InMemoryProtocolDataStore, + store::Store, + transaction_source::{ + in_memory::{InMemoryEnvironment, InMemoryReadTransaction}, + Transaction, TransactionSource, + }, + trie::{operations::create_hashed_empty_trie, Trie}, + trie_store::{ + in_memory::InMemoryTrieStore, + operations::{self, read, ReadResult, WriteResult}, + }, +}; + +pub struct InMemoryGlobalState { + pub environment: Arc, + pub trie_store: Arc, + pub protocol_data_store: Arc, + pub empty_root_hash: Blake2bHash, +} + +/// Represents a "view" of global state at a particular root hash. +pub struct InMemoryGlobalStateView { + pub environment: Arc, + pub store: Arc, + pub root_hash: Blake2bHash, +} + +impl InMemoryGlobalState { + /// Creates an empty state. + pub fn empty() -> Result { + let environment = Arc::new(InMemoryEnvironment::new()); + let trie_store = Arc::new(InMemoryTrieStore::new(&environment, None)); + let protocol_data_store = Arc::new(InMemoryProtocolDataStore::new(&environment, None)); + let root_hash: Blake2bHash = { + let (root_hash, root) = create_hashed_empty_trie::()?; + let mut txn = environment.create_read_write_txn()?; + trie_store.put(&mut txn, &root_hash, &root)?; + txn.commit()?; + root_hash + }; + Ok(InMemoryGlobalState::new( + environment, + trie_store, + protocol_data_store, + root_hash, + )) + } + + /// Creates a state from an existing environment, trie_Store, and root_hash. + /// Intended to be used for testing. + pub(crate) fn new( + environment: Arc, + trie_store: Arc, + protocol_data_store: Arc, + empty_root_hash: Blake2bHash, + ) -> Self { + InMemoryGlobalState { + environment, + trie_store, + protocol_data_store, + empty_root_hash, + } + } + + /// Creates a state from a given set of `Key, StoredValue` pairs. + pub fn from_pairs( + correlation_id: CorrelationId, + pairs: &[(Key, StoredValue)], + ) -> Result<(Self, Blake2bHash), error::Error> { + let state = InMemoryGlobalState::empty()?; + let mut current_root = state.empty_root_hash; + { + let mut txn = state.environment.create_read_write_txn()?; + for (key, value) in pairs { + let key = key.normalize(); + match operations::write::<_, _, _, InMemoryTrieStore, in_memory::Error>( + correlation_id, + &mut txn, + &state.trie_store, + ¤t_root, + &key, + value, + )? { + WriteResult::Written(root_hash) => { + current_root = root_hash; + } + WriteResult::AlreadyExists => (), + WriteResult::RootNotFound => panic!("InMemoryGlobalState has invalid root"), + } + } + txn.commit()?; + } + Ok((state, current_root)) + } +} + +impl StateReader for InMemoryGlobalStateView { + type Error = error::Error; + + fn read( + &self, + correlation_id: CorrelationId, + key: &Key, + ) -> Result, Self::Error> { + let txn = self.environment.create_read_txn()?; + let ret = match read::< + Key, + StoredValue, + InMemoryReadTransaction, + InMemoryTrieStore, + Self::Error, + >( + correlation_id, + &txn, + self.store.deref(), + &self.root_hash, + key, + )? { + ReadResult::Found(value) => Some(value), + ReadResult::NotFound => None, + ReadResult::RootNotFound => panic!("InMemoryGlobalState has invalid root"), + }; + txn.commit()?; + Ok(ret) + } +} + +impl StateProvider for InMemoryGlobalState { + type Error = error::Error; + + type Reader = InMemoryGlobalStateView; + + fn checkout(&self, prestate_hash: Blake2bHash) -> Result, Self::Error> { + let txn = self.environment.create_read_txn()?; + let maybe_root: Option> = + self.trie_store.get(&txn, &prestate_hash)?; + let maybe_state = maybe_root.map(|_| InMemoryGlobalStateView { + environment: Arc::clone(&self.environment), + store: Arc::clone(&self.trie_store), + root_hash: prestate_hash, + }); + txn.commit()?; + Ok(maybe_state) + } + + fn commit( + &self, + correlation_id: CorrelationId, + prestate_hash: Blake2bHash, + effects: AdditiveMap, + ) -> Result { + let commit_result = commit::( + &self.environment, + &self.trie_store, + correlation_id, + prestate_hash, + effects, + )?; + Ok(commit_result) + } + + fn put_protocol_data( + &self, + protocol_version: ProtocolVersion, + protocol_data: &ProtocolData, + ) -> Result<(), Self::Error> { + let mut txn = self.environment.create_read_write_txn()?; + self.protocol_data_store + .put(&mut txn, &protocol_version, protocol_data)?; + txn.commit().map_err(Into::into) + } + + fn get_protocol_data( + &self, + protocol_version: ProtocolVersion, + ) -> Result, Self::Error> { + let txn = self.environment.create_read_txn()?; + let result = self.protocol_data_store.get(&txn, &protocol_version)?; + txn.commit()?; + Ok(result) + } + + fn empty_root(&self) -> Blake2bHash { + self.empty_root_hash + } +} + +#[cfg(test)] +mod tests { + use types::{account::AccountHash, CLValue}; + + use super::*; + + #[derive(Debug, Clone)] + struct TestPair { + key: Key, + value: StoredValue, + } + + fn create_test_pairs() -> [TestPair; 2] { + [ + TestPair { + key: Key::Account(AccountHash::new([1_u8; 32])), + value: StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()), + }, + TestPair { + key: Key::Account(AccountHash::new([2_u8; 32])), + value: StoredValue::CLValue(CLValue::from_t(2_i32).unwrap()), + }, + ] + } + + fn create_test_pairs_updated() -> [TestPair; 3] { + [ + TestPair { + key: Key::Account(AccountHash::new([1u8; 32])), + value: StoredValue::CLValue(CLValue::from_t("one".to_string()).unwrap()), + }, + TestPair { + key: Key::Account(AccountHash::new([2u8; 32])), + value: StoredValue::CLValue(CLValue::from_t("two".to_string()).unwrap()), + }, + TestPair { + key: Key::Account(AccountHash::new([3u8; 32])), + value: StoredValue::CLValue(CLValue::from_t(3_i32).unwrap()), + }, + ] + } + + fn create_test_state() -> (InMemoryGlobalState, Blake2bHash) { + InMemoryGlobalState::from_pairs( + CorrelationId::new(), + &create_test_pairs() + .iter() + .cloned() + .map(|TestPair { key, value }| (key, value)) + .collect::>(), + ) + .unwrap() + } + + #[test] + fn reads_from_a_checkout_return_expected_values() { + let correlation_id = CorrelationId::new(); + let (state, root_hash) = create_test_state(); + let checkout = state.checkout(root_hash).unwrap().unwrap(); + for TestPair { key, value } in create_test_pairs().iter().cloned() { + assert_eq!(Some(value), checkout.read(correlation_id, &key).unwrap()); + } + } + + #[test] + fn checkout_fails_if_unknown_hash_is_given() { + let (state, _) = create_test_state(); + let fake_hash: Blake2bHash = [1u8; 32].into(); + let result = state.checkout(fake_hash).unwrap(); + assert!(result.is_none()); + } + + #[test] + fn commit_updates_state() { + let correlation_id = CorrelationId::new(); + + let test_pairs_updated = create_test_pairs_updated(); + + let (state, root_hash) = create_test_state(); + + let effects: AdditiveMap = test_pairs_updated + .iter() + .cloned() + .map(|TestPair { key, value }| (key, Transform::Write(value))) + .collect(); + + let updated_hash = match state.commit(correlation_id, root_hash, effects).unwrap() { + CommitResult::Success { state_root, .. } => state_root, + _ => panic!("commit failed"), + }; + + let updated_checkout = state.checkout(updated_hash).unwrap().unwrap(); + + for TestPair { key, value } in test_pairs_updated.iter().cloned() { + assert_eq!( + Some(value), + updated_checkout.read(correlation_id, &key).unwrap() + ); + } + } + + #[test] + fn commit_updates_state_and_original_state_stays_intact() { + let correlation_id = CorrelationId::new(); + let test_pairs_updated = create_test_pairs_updated(); + + let (state, root_hash) = create_test_state(); + + let effects: AdditiveMap = { + let mut tmp = AdditiveMap::new(); + for TestPair { key, value } in &test_pairs_updated { + tmp.insert(*key, Transform::Write(value.to_owned())); + } + tmp + }; + + let updated_hash = match state.commit(correlation_id, root_hash, effects).unwrap() { + CommitResult::Success { state_root, .. } => state_root, + _ => panic!("commit failed"), + }; + + let updated_checkout = state.checkout(updated_hash).unwrap().unwrap(); + for TestPair { key, value } in test_pairs_updated.iter().cloned() { + assert_eq!( + Some(value), + updated_checkout.read(correlation_id, &key).unwrap() + ); + } + + let original_checkout = state.checkout(root_hash).unwrap().unwrap(); + for TestPair { key, value } in create_test_pairs().iter().cloned() { + assert_eq!( + Some(value), + original_checkout.read(correlation_id, &key).unwrap() + ); + } + assert_eq!( + None, + original_checkout + .read(correlation_id, &test_pairs_updated[2].key) + .unwrap() + ); + } + + #[test] + fn initial_state_has_the_expected_hash() { + let correlation_id = CorrelationId::new(); + let expected_bytes = vec![ + 197, 117, 38, 12, 241, 62, 54, 241, 121, 165, 11, 8, 130, 189, 100, 252, 4, 102, 236, + 210, 91, 221, 123, 200, 135, 102, 194, 204, 46, 76, 13, 254, + ]; + let (_, root_hash) = InMemoryGlobalState::from_pairs(correlation_id, &[]).unwrap(); + assert_eq!(expected_bytes, root_hash.to_vec()) + } +} diff --git a/node/src/contract_storage/global_state/lmdb.rs b/node/src/contract_storage/global_state/lmdb.rs new file mode 100644 index 0000000000..7f9ea9977f --- /dev/null +++ b/node/src/contract_storage/global_state/lmdb.rs @@ -0,0 +1,342 @@ +use std::{ops::Deref, sync::Arc}; + +use crate::contract_shared::{ + additive_map::AdditiveMap, + newtypes::{Blake2bHash, CorrelationId}, + stored_value::StoredValue, + transform::Transform, +}; +use types::{Key, ProtocolVersion}; + +use crate::contract_storage::{ + error, + global_state::{commit, CommitResult, StateProvider, StateReader}, + protocol_data::ProtocolData, + protocol_data_store::lmdb::LmdbProtocolDataStore, + store::Store, + transaction_source::{lmdb::LmdbEnvironment, Transaction, TransactionSource}, + trie::{operations::create_hashed_empty_trie, Trie}, + trie_store::{ + lmdb::LmdbTrieStore, + operations::{read, ReadResult}, + }, +}; + +pub struct LmdbGlobalState { + pub environment: Arc, + pub trie_store: Arc, + pub protocol_data_store: Arc, + pub empty_root_hash: Blake2bHash, +} + +/// Represents a "view" of global state at a particular root hash. +pub struct LmdbGlobalStateView { + pub environment: Arc, + pub store: Arc, + pub root_hash: Blake2bHash, +} + +impl LmdbGlobalState { + /// Creates an empty state from an existing environment and trie_store. + pub fn empty( + environment: Arc, + trie_store: Arc, + protocol_data_store: Arc, + ) -> Result { + let root_hash: Blake2bHash = { + let (root_hash, root) = create_hashed_empty_trie::()?; + let mut txn = environment.create_read_write_txn()?; + trie_store.put(&mut txn, &root_hash, &root)?; + txn.commit()?; + root_hash + }; + Ok(LmdbGlobalState::new( + environment, + trie_store, + protocol_data_store, + root_hash, + )) + } + + /// Creates a state from an existing environment, store, and root_hash. + /// Intended to be used for testing. + pub(crate) fn new( + environment: Arc, + trie_store: Arc, + protocol_data_store: Arc, + empty_root_hash: Blake2bHash, + ) -> Self { + LmdbGlobalState { + environment, + trie_store, + protocol_data_store, + empty_root_hash, + } + } +} + +impl StateReader for LmdbGlobalStateView { + type Error = error::Error; + + fn read( + &self, + correlation_id: CorrelationId, + key: &Key, + ) -> Result, Self::Error> { + let txn = self.environment.create_read_txn()?; + let ret = match read::( + correlation_id, + &txn, + self.store.deref(), + &self.root_hash, + key, + )? { + ReadResult::Found(value) => Some(value), + ReadResult::NotFound => None, + ReadResult::RootNotFound => panic!("LmdbGlobalState has invalid root"), + }; + txn.commit()?; + Ok(ret) + } +} + +impl StateProvider for LmdbGlobalState { + type Error = error::Error; + + type Reader = LmdbGlobalStateView; + + fn checkout(&self, state_hash: Blake2bHash) -> Result, Self::Error> { + let txn = self.environment.create_read_txn()?; + let maybe_root: Option> = self.trie_store.get(&txn, &state_hash)?; + let maybe_state = maybe_root.map(|_| LmdbGlobalStateView { + environment: Arc::clone(&self.environment), + store: Arc::clone(&self.trie_store), + root_hash: state_hash, + }); + txn.commit()?; + Ok(maybe_state) + } + + fn commit( + &self, + correlation_id: CorrelationId, + prestate_hash: Blake2bHash, + effects: AdditiveMap, + ) -> Result { + let commit_result = commit::( + &self.environment, + &self.trie_store, + correlation_id, + prestate_hash, + effects, + )?; + Ok(commit_result) + } + + fn put_protocol_data( + &self, + protocol_version: ProtocolVersion, + protocol_data: &ProtocolData, + ) -> Result<(), Self::Error> { + let mut txn = self.environment.create_read_write_txn()?; + self.protocol_data_store + .put(&mut txn, &protocol_version, protocol_data)?; + txn.commit().map_err(Into::into) + } + + fn get_protocol_data( + &self, + protocol_version: ProtocolVersion, + ) -> Result, Self::Error> { + let txn = self.environment.create_read_txn()?; + let result = self.protocol_data_store.get(&txn, &protocol_version)?; + txn.commit()?; + Ok(result) + } + + fn empty_root(&self) -> Blake2bHash { + self.empty_root_hash + } +} + +#[cfg(test)] +mod tests { + use lmdb::DatabaseFlags; + use tempfile::tempdir; + + use types::{account::AccountHash, CLValue}; + + use crate::contract_storage::{ + trie_store::operations::{write, WriteResult}, + TEST_MAP_SIZE, + }; + + use super::*; + + #[derive(Debug, Clone)] + struct TestPair { + key: Key, + value: StoredValue, + } + + fn create_test_pairs() -> [TestPair; 2] { + [ + TestPair { + key: Key::Account(AccountHash::new([1_u8; 32])), + value: StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()), + }, + TestPair { + key: Key::Account(AccountHash::new([2_u8; 32])), + value: StoredValue::CLValue(CLValue::from_t(2_i32).unwrap()), + }, + ] + } + + fn create_test_pairs_updated() -> [TestPair; 3] { + [ + TestPair { + key: Key::Account(AccountHash::new([1u8; 32])), + value: StoredValue::CLValue(CLValue::from_t("one".to_string()).unwrap()), + }, + TestPair { + key: Key::Account(AccountHash::new([2u8; 32])), + value: StoredValue::CLValue(CLValue::from_t("two".to_string()).unwrap()), + }, + TestPair { + key: Key::Account(AccountHash::new([3u8; 32])), + value: StoredValue::CLValue(CLValue::from_t(3_i32).unwrap()), + }, + ] + } + + fn create_test_state() -> (LmdbGlobalState, Blake2bHash) { + let correlation_id = CorrelationId::new(); + let _temp_dir = tempdir().unwrap(); + let environment = Arc::new( + LmdbEnvironment::new(&_temp_dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(), + ); + let trie_store = + Arc::new(LmdbTrieStore::new(&environment, None, DatabaseFlags::empty()).unwrap()); + let protocol_data_store = Arc::new( + LmdbProtocolDataStore::new(&environment, None, DatabaseFlags::empty()).unwrap(), + ); + let ret = LmdbGlobalState::empty(environment, trie_store, protocol_data_store).unwrap(); + let mut current_root = ret.empty_root_hash; + { + let mut txn = ret.environment.create_read_write_txn().unwrap(); + + for TestPair { key, value } in &create_test_pairs() { + match write::<_, _, _, LmdbTrieStore, error::Error>( + correlation_id, + &mut txn, + &ret.trie_store, + ¤t_root, + key, + value, + ) + .unwrap() + { + WriteResult::Written(root_hash) => { + current_root = root_hash; + } + WriteResult::AlreadyExists => (), + WriteResult::RootNotFound => panic!("LmdbGlobalState has invalid root"), + } + } + + txn.commit().unwrap(); + } + (ret, current_root) + } + + #[test] + fn reads_from_a_checkout_return_expected_values() { + let correlation_id = CorrelationId::new(); + let (state, root_hash) = create_test_state(); + let checkout = state.checkout(root_hash).unwrap().unwrap(); + for TestPair { key, value } in create_test_pairs().iter().cloned() { + assert_eq!(Some(value), checkout.read(correlation_id, &key).unwrap()); + } + } + + #[test] + fn checkout_fails_if_unknown_hash_is_given() { + let (state, _) = create_test_state(); + let fake_hash: Blake2bHash = [1u8; 32].into(); + let result = state.checkout(fake_hash).unwrap(); + assert!(result.is_none()); + } + + #[test] + fn commit_updates_state() { + let correlation_id = CorrelationId::new(); + let test_pairs_updated = create_test_pairs_updated(); + + let (state, root_hash) = create_test_state(); + + let effects: AdditiveMap = { + let mut tmp = AdditiveMap::new(); + for TestPair { key, value } in &test_pairs_updated { + tmp.insert(*key, Transform::Write(value.to_owned())); + } + tmp + }; + + let updated_hash = match state.commit(correlation_id, root_hash, effects).unwrap() { + CommitResult::Success { state_root, .. } => state_root, + _ => panic!("commit failed"), + }; + + let updated_checkout = state.checkout(updated_hash).unwrap().unwrap(); + + for TestPair { key, value } in test_pairs_updated.iter().cloned() { + assert_eq!( + Some(value), + updated_checkout.read(correlation_id, &key).unwrap() + ); + } + } + + #[test] + fn commit_updates_state_and_original_state_stays_intact() { + let correlation_id = CorrelationId::new(); + let test_pairs_updated = create_test_pairs_updated(); + + let (state, root_hash) = create_test_state(); + + let effects: AdditiveMap = { + let mut tmp = AdditiveMap::new(); + for TestPair { key, value } in &test_pairs_updated { + tmp.insert(*key, Transform::Write(value.to_owned())); + } + tmp + }; + + let updated_hash = match state.commit(correlation_id, root_hash, effects).unwrap() { + CommitResult::Success { state_root, .. } => state_root, + _ => panic!("commit failed"), + }; + + let updated_checkout = state.checkout(updated_hash).unwrap().unwrap(); + for TestPair { key, value } in test_pairs_updated.iter().cloned() { + assert_eq!( + Some(value), + updated_checkout.read(correlation_id, &key).unwrap() + ); + } + + let original_checkout = state.checkout(root_hash).unwrap().unwrap(); + for TestPair { key, value } in create_test_pairs().iter().cloned() { + assert_eq!( + Some(value), + original_checkout.read(correlation_id, &key).unwrap() + ); + } + assert_eq!( + None, + original_checkout + .read(correlation_id, &test_pairs_updated[2].key) + .unwrap() + ); + } +} diff --git a/node/src/contract_storage/global_state/mod.rs b/node/src/contract_storage/global_state/mod.rs new file mode 100644 index 0000000000..a4741ce397 --- /dev/null +++ b/node/src/contract_storage/global_state/mod.rs @@ -0,0 +1,219 @@ +pub mod in_memory; +pub mod lmdb; + +use std::{collections::HashMap, fmt, hash::BuildHasher, time::Instant}; + +use crate::contract_shared::{ + additive_map::AdditiveMap, + logging::{log_duration, log_metric}, + newtypes::{Blake2bHash, CorrelationId}, + stored_value::StoredValue, + transform::{self, Transform}, + TypeMismatch, +}; +use types::{account::AccountHash, bytesrepr, Key, ProtocolVersion, U512}; + +use crate::contract_storage::{ + protocol_data::ProtocolData, + transaction_source::{Transaction, TransactionSource}, + trie::Trie, + trie_store::{ + operations::{read, write, ReadResult, WriteResult}, + TrieStore, + }, + GAUGE_METRIC_KEY, +}; + +const GLOBAL_STATE_COMMIT_READS: &str = "global_state_commit_reads"; +const GLOBAL_STATE_COMMIT_WRITES: &str = "global_state_commit_writes"; +const GLOBAL_STATE_COMMIT_DURATION: &str = "global_state_commit_duration"; +const GLOBAL_STATE_COMMIT_READ_DURATION: &str = "global_state_commit_read_duration"; +const GLOBAL_STATE_COMMIT_WRITE_DURATION: &str = "global_state_commit_write_duration"; +const COMMIT: &str = "commit"; + +/// A reader of state +pub trait StateReader { + /// An error which occurs when reading state + type Error; + + /// Returns the state value from the corresponding key + fn read(&self, correlation_id: CorrelationId, key: &K) -> Result, Self::Error>; +} + +#[derive(Debug)] +pub enum CommitResult { + RootNotFound, + Success { + state_root: Blake2bHash, + bonded_validators: HashMap, + }, + KeyNotFound(Key), + TypeMismatch(TypeMismatch), + Serialization(bytesrepr::Error), +} + +impl fmt::Display for CommitResult { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + CommitResult::RootNotFound => write!(f, "Root not found"), + CommitResult::Success { + state_root, + bonded_validators, + } => write!( + f, + "Success: state_root: {}, bonded_validators: {:?}", + state_root, bonded_validators + ), + CommitResult::KeyNotFound(key) => write!(f, "Key not found: {}", key), + CommitResult::TypeMismatch(type_mismatch) => { + write!(f, "Type mismatch: {:?}", type_mismatch) + } + CommitResult::Serialization(error) => write!(f, "Serialization: {:?}", error), + } + } +} + +impl From for CommitResult { + fn from(error: transform::Error) -> Self { + match error { + transform::Error::TypeMismatch(type_mismatch) => { + CommitResult::TypeMismatch(type_mismatch) + } + transform::Error::Serialization(error) => CommitResult::Serialization(error), + } + } +} + +pub trait StateProvider { + type Error; + type Reader: StateReader; + + /// Checkouts to the post state of a specific block. + fn checkout(&self, state_hash: Blake2bHash) -> Result, Self::Error>; + + /// Applies changes and returns a new post state hash. + /// block_hash is used for computing a deterministic and unique keys. + fn commit( + &self, + correlation_id: CorrelationId, + state_hash: Blake2bHash, + effects: AdditiveMap, + ) -> Result; + + fn put_protocol_data( + &self, + protocol_version: ProtocolVersion, + protocol_data: &ProtocolData, + ) -> Result<(), Self::Error>; + + fn get_protocol_data( + &self, + protocol_version: ProtocolVersion, + ) -> Result, Self::Error>; + + fn empty_root(&self) -> Blake2bHash; +} + +pub fn commit<'a, R, S, H, E>( + environment: &'a R, + store: &S, + correlation_id: CorrelationId, + prestate_hash: Blake2bHash, + effects: AdditiveMap, +) -> Result +where + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, + H: BuildHasher, +{ + let mut txn = environment.create_read_write_txn()?; + let mut state_root = prestate_hash; + + let maybe_root: Option> = store.get(&txn, &state_root)?; + + if maybe_root.is_none() { + return Ok(CommitResult::RootNotFound); + }; + + let start = Instant::now(); + let mut reads: i32 = 0; + let mut writes: i32 = 0; + + for (key, transform) in effects.into_iter() { + let read_result = read::<_, _, _, _, E>(correlation_id, &txn, store, &state_root, &key)?; + + log_duration( + correlation_id, + GLOBAL_STATE_COMMIT_READ_DURATION, + COMMIT, + start.elapsed(), + ); + + reads += 1; + + let value = match (read_result, transform) { + (ReadResult::NotFound, Transform::Write(new_value)) => new_value, + (ReadResult::NotFound, _) => { + return Ok(CommitResult::KeyNotFound(key)); + } + (ReadResult::Found(current_value), transform) => match transform.apply(current_value) { + Ok(updated_value) => updated_value, + Err(err) => return Ok(err.into()), + }, + _x @ (ReadResult::RootNotFound, _) => panic!(stringify!(_x._1)), + }; + + let write_result = + write::<_, _, _, _, E>(correlation_id, &mut txn, store, &state_root, &key, &value)?; + + log_duration( + correlation_id, + GLOBAL_STATE_COMMIT_WRITE_DURATION, + COMMIT, + start.elapsed(), + ); + + match write_result { + WriteResult::Written(root_hash) => { + state_root = root_hash; + writes += 1; + } + WriteResult::AlreadyExists => (), + _x @ WriteResult::RootNotFound => panic!(stringify!(_x)), + } + } + + txn.commit()?; + + log_duration( + correlation_id, + GLOBAL_STATE_COMMIT_DURATION, + COMMIT, + start.elapsed(), + ); + + log_metric( + correlation_id, + GLOBAL_STATE_COMMIT_READS, + COMMIT, + GAUGE_METRIC_KEY, + f64::from(reads), + ); + + log_metric( + correlation_id, + GLOBAL_STATE_COMMIT_WRITES, + COMMIT, + GAUGE_METRIC_KEY, + f64::from(writes), + ); + + let bonded_validators = Default::default(); + + Ok(CommitResult::Success { + state_root, + bonded_validators, + }) +} diff --git a/node/src/contract_storage/protocol_data.rs b/node/src/contract_storage/protocol_data.rs new file mode 100644 index 0000000000..7619618e92 --- /dev/null +++ b/node/src/contract_storage/protocol_data.rs @@ -0,0 +1,319 @@ +use crate::contract_shared::wasm_costs::{WasmCosts, WASM_COSTS_SERIALIZED_LENGTH}; +use std::collections::BTreeMap; +use types::{ + bytesrepr::{self, FromBytes, ToBytes}, + ContractHash, HashAddr, KEY_HASH_LENGTH, +}; + +const PROTOCOL_DATA_SERIALIZED_LENGTH: usize = WASM_COSTS_SERIALIZED_LENGTH + 3 * KEY_HASH_LENGTH; +const DEFAULT_ADDRESS: [u8; 32] = [0; 32]; + +/// Represents a protocol's data. Intended to be associated with a given protocol version. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ProtocolData { + wasm_costs: WasmCosts, + mint: ContractHash, + proof_of_stake: ContractHash, + standard_payment: ContractHash, +} + +/// Provides a default instance with non existing urefs and empty costs table. +/// +/// Used in contexts where PoS or Mint contract is not ready yet, and pos, and +/// mint installers are ran. For use with caution. +impl Default for ProtocolData { + fn default() -> ProtocolData { + ProtocolData { + wasm_costs: WasmCosts::default(), + mint: DEFAULT_ADDRESS, + proof_of_stake: DEFAULT_ADDRESS, + standard_payment: DEFAULT_ADDRESS, + } + } +} + +impl ProtocolData { + /// Creates a new [`ProtocolData`] value from a given [`WasmCosts`] value. + pub fn new( + wasm_costs: WasmCosts, + mint: ContractHash, + proof_of_stake: ContractHash, + standard_payment: ContractHash, + ) -> Self { + ProtocolData { + wasm_costs, + mint, + proof_of_stake, + standard_payment, + } + } + + /// Creates a new, partially-valid [`ProtocolData`] value where only the mint URef is known. + /// + /// Used during `commit_genesis` before all system contracts' URefs are known. + pub fn partial_with_mint(mint: ContractHash) -> Self { + ProtocolData { + mint, + ..Default::default() + } + } + + /// Creates a new, partially-valid [`ProtocolData`] value where all but the standard payment + /// uref is known. + /// + /// Used during `commit_genesis` before all system contracts' URefs are known. + pub fn partial_without_standard_payment( + wasm_costs: WasmCosts, + mint: ContractHash, + proof_of_stake: ContractHash, + ) -> Self { + ProtocolData { + wasm_costs, + mint, + proof_of_stake, + ..Default::default() + } + } + + /// Gets the [`WasmCosts`] value from a given [`ProtocolData`] value. + pub fn wasm_costs(&self) -> &WasmCosts { + &self.wasm_costs + } + + pub fn mint(&self) -> ContractHash { + self.mint + } + + pub fn proof_of_stake(&self) -> ContractHash { + self.proof_of_stake + } + + pub fn standard_payment(&self) -> ContractHash { + self.standard_payment + } + + /// Retrieves all valid system contracts stored in protocol version + pub fn system_contracts(&self) -> Vec { + let mut vec = Vec::with_capacity(3); + if self.mint != DEFAULT_ADDRESS { + vec.push(self.mint) + } + if self.proof_of_stake != DEFAULT_ADDRESS { + vec.push(self.proof_of_stake) + } + if self.standard_payment != DEFAULT_ADDRESS { + vec.push(self.standard_payment) + } + vec + } + + pub fn update_from(&mut self, updates: BTreeMap) -> bool { + for (old_hash, new_hash) in updates { + if old_hash == self.mint { + self.mint = new_hash; + } else if old_hash == self.proof_of_stake { + self.proof_of_stake = new_hash; + } else if old_hash == self.standard_payment { + self.standard_payment = new_hash; + } else { + return false; + } + } + true + } +} + +impl ToBytes for ProtocolData { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut ret = bytesrepr::unchecked_allocate_buffer(self); + ret.append(&mut self.wasm_costs.to_bytes()?); + ret.append(&mut self.mint.to_bytes()?); + ret.append(&mut self.proof_of_stake.to_bytes()?); + ret.append(&mut self.standard_payment.to_bytes()?); + Ok(ret) + } + + fn serialized_length(&self) -> usize { + PROTOCOL_DATA_SERIALIZED_LENGTH + } +} + +impl FromBytes for ProtocolData { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (wasm_costs, rem) = WasmCosts::from_bytes(bytes)?; + let (mint, rem) = HashAddr::from_bytes(rem)?; + let (proof_of_stake, rem) = HashAddr::from_bytes(rem)?; + let (standard_payment, rem) = HashAddr::from_bytes(rem)?; + + Ok(( + ProtocolData { + wasm_costs, + mint, + proof_of_stake, + standard_payment, + }, + rem, + )) + } +} + +#[cfg(test)] +pub(crate) mod gens { + use proptest::prop_compose; + + use crate::contract_shared::wasm_costs::gens as wasm_costs_gens; + use types::gens; + + use super::ProtocolData; + + prop_compose! { + pub fn protocol_data_arb()( + wasm_costs in wasm_costs_gens::wasm_costs_arb(), + mint in gens::u8_slice_32(), + proof_of_stake in gens::u8_slice_32(), + standard_payment in gens::u8_slice_32(), + ) -> ProtocolData { + ProtocolData { + wasm_costs, + mint, + proof_of_stake, + standard_payment, + } + } + } +} + +#[cfg(test)] +mod tests { + use proptest::proptest; + + use crate::contract_shared::wasm_costs::WasmCosts; + use types::{bytesrepr, ContractHash}; + + use super::{gens, ProtocolData}; + + fn wasm_costs_mock() -> WasmCosts { + WasmCosts { + regular: 1, + div: 16, + mul: 4, + mem: 2, + initial_mem: 4096, + grow_mem: 8192, + memcpy: 1, + max_stack_height: 64 * 1024, + opcodes_mul: 3, + opcodes_div: 8, + } + } + + fn wasm_costs_free() -> WasmCosts { + WasmCosts { + regular: 0, + div: 0, + mul: 0, + mem: 0, + initial_mem: 4096, + grow_mem: 8192, + memcpy: 0, + max_stack_height: 64 * 1024, + opcodes_mul: 1, + opcodes_div: 1, + } + } + + #[test] + fn should_serialize_and_deserialize() { + let mock = { + let costs = wasm_costs_mock(); + let mint_reference = [1u8; 32]; + let proof_of_stake_reference = [2u8; 32]; + let standard_payment_reference = [3u8; 32]; + ProtocolData::new( + costs, + mint_reference, + proof_of_stake_reference, + standard_payment_reference, + ) + }; + let free = { + let costs = wasm_costs_free(); + let mint_reference = [0u8; 32]; + let proof_of_stake_reference = [1u8; 32]; + let standard_payment_reference = [2u8; 32]; + ProtocolData::new( + costs, + mint_reference, + proof_of_stake_reference, + standard_payment_reference, + ) + }; + bytesrepr::test_serialization_roundtrip(&mock); + bytesrepr::test_serialization_roundtrip(&free); + } + + #[test] + fn should_return_all_system_contracts() { + let mint_reference = [1u8; 32]; + let proof_of_stake_reference = [2u8; 32]; + let standard_payment_reference = [3u8; 32]; + let protocol_data = { + let costs = wasm_costs_mock(); + ProtocolData::new( + costs, + mint_reference, + proof_of_stake_reference, + standard_payment_reference, + ) + }; + + let actual = { + let mut items = protocol_data.system_contracts(); + items.sort(); + items + }; + + assert_eq!(actual.len(), 3); + assert_eq!(actual[0], mint_reference); + assert_eq!(actual[1], proof_of_stake_reference); + assert_eq!(actual[2], standard_payment_reference); + } + + #[test] + fn should_return_only_valid_system_contracts() { + let expected: Vec = vec![]; + assert_eq!(ProtocolData::default().system_contracts(), expected); + + let mint_reference = [0u8; 32]; // <-- invalid addr + let proof_of_stake_reference = [2u8; 32]; + let standard_payment_reference = [3u8; 32]; + let protocol_data = { + let costs = wasm_costs_mock(); + ProtocolData::new( + costs, + mint_reference, + proof_of_stake_reference, + standard_payment_reference, + ) + }; + + let actual = { + let mut items = protocol_data.system_contracts(); + items.sort(); + items + }; + + assert_eq!(actual.len(), 2); + assert_eq!(actual[0], proof_of_stake_reference); + assert_eq!(actual[1], standard_payment_reference); + } + + proptest! { + #[test] + fn should_serialize_and_deserialize_with_arbitrary_values( + protocol_data in gens::protocol_data_arb() + ) { + bytesrepr::test_serialization_roundtrip(&protocol_data); + } + } +} diff --git a/node/src/contract_storage/protocol_data_store/in_memory.rs b/node/src/contract_storage/protocol_data_store/in_memory.rs new file mode 100644 index 0000000000..d2046a68f2 --- /dev/null +++ b/node/src/contract_storage/protocol_data_store/in_memory.rs @@ -0,0 +1,36 @@ +use types::ProtocolVersion; + +use crate::contract_storage::{ + error::in_memory::Error, + protocol_data::ProtocolData, + protocol_data_store::{self, ProtocolDataStore}, + store::Store, + transaction_source::in_memory::InMemoryEnvironment, +}; + +/// An in-memory protocol data store +pub struct InMemoryProtocolDataStore { + maybe_name: Option, +} + +impl InMemoryProtocolDataStore { + pub fn new(_env: &InMemoryEnvironment, maybe_name: Option<&str>) -> Self { + let name = maybe_name + .map(|name| format!("{}-{}", protocol_data_store::NAME, name)) + .unwrap_or_else(|| String::from(protocol_data_store::NAME)); + InMemoryProtocolDataStore { + maybe_name: Some(name), + } + } +} + +impl Store for InMemoryProtocolDataStore { + type Error = Error; + type Handle = Option; + + fn handle(&self) -> Self::Handle { + self.maybe_name.to_owned() + } +} + +impl ProtocolDataStore for InMemoryProtocolDataStore {} diff --git a/node/src/contract_storage/protocol_data_store/lmdb.rs b/node/src/contract_storage/protocol_data_store/lmdb.rs new file mode 100644 index 0000000000..0c9502ce26 --- /dev/null +++ b/node/src/contract_storage/protocol_data_store/lmdb.rs @@ -0,0 +1,54 @@ +use lmdb::{Database, DatabaseFlags}; +use types::ProtocolVersion; + +use crate::contract_storage::{ + error, + protocol_data::ProtocolData, + protocol_data_store::{self, ProtocolDataStore}, + store::Store, + transaction_source::lmdb::LmdbEnvironment, +}; + +/// An LMDB-backed protocol data store. +/// +/// Wraps [`lmdb::Database`]. +#[derive(Debug, Clone)] +pub struct LmdbProtocolDataStore { + db: Database, +} + +impl LmdbProtocolDataStore { + pub fn new( + env: &LmdbEnvironment, + maybe_name: Option<&str>, + flags: DatabaseFlags, + ) -> Result { + let name = Self::name(maybe_name); + let db = env.env().create_db(Some(&name), flags)?; + Ok(LmdbProtocolDataStore { db }) + } + + pub fn open(env: &LmdbEnvironment, maybe_name: Option<&str>) -> Result { + let name = Self::name(maybe_name); + let db = env.env().open_db(Some(&name))?; + Ok(LmdbProtocolDataStore { db }) + } + + fn name(maybe_name: Option<&str>) -> String { + maybe_name + .map(|name| format!("{}-{}", protocol_data_store::NAME, name)) + .unwrap_or_else(|| String::from(protocol_data_store::NAME)) + } +} + +impl Store for LmdbProtocolDataStore { + type Error = error::Error; + + type Handle = Database; + + fn handle(&self) -> Self::Handle { + self.db + } +} + +impl ProtocolDataStore for LmdbProtocolDataStore {} diff --git a/node/src/contract_storage/protocol_data_store/mod.rs b/node/src/contract_storage/protocol_data_store/mod.rs new file mode 100644 index 0000000000..ea25a90310 --- /dev/null +++ b/node/src/contract_storage/protocol_data_store/mod.rs @@ -0,0 +1,15 @@ +//! A store for persisting [`ProtocolData`](contract::value::ProtocolVersion) values at their +//! protocol versions. +use types::ProtocolVersion; + +pub mod in_memory; +pub mod lmdb; +#[cfg(test)] +mod tests; + +use crate::contract_storage::{protocol_data::ProtocolData, store::Store}; + +const NAME: &str = "PROTOCOL_DATA_STORE"; + +/// An entity which persists [`ProtocolData`] values at their protocol versions. +pub trait ProtocolDataStore: Store {} diff --git a/node/src/contract_storage/protocol_data_store/tests/mod.rs b/node/src/contract_storage/protocol_data_store/tests/mod.rs new file mode 100644 index 0000000000..709a570c34 --- /dev/null +++ b/node/src/contract_storage/protocol_data_store/tests/mod.rs @@ -0,0 +1 @@ +mod proptests; diff --git a/node/src/contract_storage/protocol_data_store/tests/proptests.rs b/node/src/contract_storage/protocol_data_store/tests/proptests.rs new file mode 100644 index 0000000000..7f98016d34 --- /dev/null +++ b/node/src/contract_storage/protocol_data_store/tests/proptests.rs @@ -0,0 +1,60 @@ +use std::{collections::BTreeMap, ops::RangeInclusive}; + +use lmdb::DatabaseFlags; +use proptest::{collection, prelude::proptest}; + +use types::{gens as gens_ext, ProtocolVersion}; + +use crate::contract_storage::{ + protocol_data::{gens, ProtocolData}, + protocol_data_store::{in_memory::InMemoryProtocolDataStore, lmdb::LmdbProtocolDataStore}, + store::tests as store_tests, + transaction_source::{in_memory::InMemoryEnvironment, lmdb::LmdbEnvironment}, + TEST_MAP_SIZE, +}; + +const DEFAULT_MIN_LENGTH: usize = 1; +const DEFAULT_MAX_LENGTH: usize = 16; + +fn get_range() -> RangeInclusive { + let start = option_env!("CL_PROTOCOL_DATA_STORE_TEST_MAP_MIN_LENGTH") + .and_then(|s| str::parse::(s).ok()) + .unwrap_or(DEFAULT_MIN_LENGTH); + let end = option_env!("CL_PROTOCOL_DATA_STORE_TEST_MAP_MAX_LENGTH") + .and_then(|s| str::parse::(s).ok()) + .unwrap_or(DEFAULT_MAX_LENGTH); + RangeInclusive::new(start, end) +} + +fn in_memory_roundtrip_succeeds(inputs: BTreeMap) -> bool { + let env = InMemoryEnvironment::new(); + let store = InMemoryProtocolDataStore::new(&env, None); + + store_tests::roundtrip_succeeds(&env, &store, inputs).unwrap() +} + +fn lmdb_roundtrip_succeeds(inputs: BTreeMap) -> bool { + let tmp_dir = tempfile::tempdir().unwrap(); + let env = LmdbEnvironment::new(&tmp_dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + let store = LmdbProtocolDataStore::new(&env, None, DatabaseFlags::empty()).unwrap(); + + let ret = store_tests::roundtrip_succeeds(&env, &store, inputs).unwrap(); + tmp_dir.close().unwrap(); + ret +} + +proptest! { + #[test] + fn prop_in_memory_roundtrip_succeeds( + m in collection::btree_map(gens_ext::protocol_version_arb(), gens::protocol_data_arb(), get_range()) + ) { + assert!(in_memory_roundtrip_succeeds(m)) + } + + #[test] + fn prop_lmdb_roundtrip_succeeds( + m in collection::btree_map(gens_ext::protocol_version_arb(), gens::protocol_data_arb(), get_range()) + ) { + assert!(lmdb_roundtrip_succeeds(m)) + } +} diff --git a/node/src/contract_storage/store/mod.rs b/node/src/contract_storage/store/mod.rs new file mode 100644 index 0000000000..b67c642644 --- /dev/null +++ b/node/src/contract_storage/store/mod.rs @@ -0,0 +1,45 @@ +mod store_ext; +#[cfg(test)] +pub(crate) mod tests; + +use types::bytesrepr::{self, FromBytes, ToBytes}; + +pub use self::store_ext::StoreExt; +use crate::contract_storage::transaction_source::{Readable, Writable}; + +pub trait Store { + type Error: From; + + type Handle; + + fn handle(&self) -> Self::Handle; + + fn get(&self, txn: &T, key: &K) -> Result, Self::Error> + where + T: Readable, + K: ToBytes, + V: FromBytes, + Self::Error: From, + { + let handle = self.handle(); + match txn.read(handle, &key.to_bytes()?)? { + None => Ok(None), + Some(value_bytes) => { + let value = bytesrepr::deserialize(value_bytes)?; + Ok(Some(value)) + } + } + } + + fn put(&self, txn: &mut T, key: &K, value: &V) -> Result<(), Self::Error> + where + T: Writable, + K: ToBytes, + V: ToBytes, + Self::Error: From, + { + let handle = self.handle(); + txn.write(handle, &key.to_bytes()?, &value.to_bytes()?) + .map_err(Into::into) + } +} diff --git a/node/src/contract_storage/store/store_ext.rs b/node/src/contract_storage/store/store_ext.rs new file mode 100644 index 0000000000..0a3e7c2ba7 --- /dev/null +++ b/node/src/contract_storage/store/store_ext.rs @@ -0,0 +1,46 @@ +use types::bytesrepr::{FromBytes, ToBytes}; + +use crate::contract_storage::{ + store::Store, + transaction_source::{Readable, Writable}, +}; + +pub trait StoreExt: Store { + fn get_many<'a, T>( + &self, + txn: &T, + keys: impl Iterator, + ) -> Result>, Self::Error> + where + T: Readable, + K: ToBytes + 'a, + V: FromBytes, + Self::Error: From, + { + let mut ret: Vec> = Vec::new(); + for key in keys { + let result = self.get(txn, key)?; + ret.push(result) + } + Ok(ret) + } + + fn put_many<'a, T>( + &self, + txn: &mut T, + pairs: impl Iterator, + ) -> Result<(), Self::Error> + where + T: Writable, + K: ToBytes + 'a, + V: ToBytes + 'a, + Self::Error: From, + { + for (key, value) in pairs { + self.put(txn, key, value)?; + } + Ok(()) + } +} + +impl> StoreExt for T {} diff --git a/node/src/contract_storage/store/tests.rs b/node/src/contract_storage/store/tests.rs new file mode 100644 index 0000000000..5eb983727c --- /dev/null +++ b/node/src/contract_storage/store/tests.rs @@ -0,0 +1,49 @@ +use std::collections::BTreeMap; + +use types::bytesrepr::{FromBytes, ToBytes}; + +use crate::contract_storage::{ + store::{Store, StoreExt}, + transaction_source::{Transaction, TransactionSource}, +}; + +// should be moved to the `store` module +fn roundtrip<'a, K, V, X, S>( + transaction_source: &'a X, + store: &S, + items: &BTreeMap, +) -> Result>, S::Error> +where + K: ToBytes, + V: ToBytes + FromBytes, + X: TransactionSource<'a, Handle = S::Handle>, + S: Store, + S::Error: From, +{ + let mut txn: X::ReadWriteTransaction = transaction_source.create_read_write_txn()?; + store.put_many(&mut txn, items.iter())?; + let result = store.get_many(&txn, items.keys()); + txn.commit()?; + result +} + +// should be moved to the `store` module +pub fn roundtrip_succeeds<'a, K, V, X, S>( + transaction_source: &'a X, + store: &S, + items: BTreeMap, +) -> Result +where + K: ToBytes, + V: ToBytes + FromBytes + Clone + PartialEq, + X: TransactionSource<'a, Handle = S::Handle>, + S: Store, + S::Error: From, +{ + let maybe_values: Vec> = roundtrip(transaction_source, store, &items)?; + let values = match maybe_values.into_iter().collect::>>() { + Some(values) => values, + None => return Ok(false), + }; + Ok(Iterator::eq(items.values(), values.iter())) +} diff --git a/node/src/contract_storage/transaction_source/in_memory.rs b/node/src/contract_storage/transaction_source/in_memory.rs new file mode 100644 index 0000000000..cd7ff48c09 --- /dev/null +++ b/node/src/contract_storage/transaction_source/in_memory.rs @@ -0,0 +1,157 @@ +use std::{ + collections::HashMap, + sync::{self, Arc, Mutex, MutexGuard}, +}; + +use crate::contract_storage::{ + error::in_memory::Error, + transaction_source::{Readable, Transaction, TransactionSource, Writable}, +}; + +/// A marker for use in a mutex which represents the capability to perform a +/// write transaction. +struct WriteCapability; + +type WriteLock<'a> = MutexGuard<'a, WriteCapability>; + +type BytesMap = HashMap, Vec>; + +type PoisonError<'a> = sync::PoisonError, BytesMap>>>; + +/// A read transaction for the in-memory trie store. +pub struct InMemoryReadTransaction { + view: HashMap, BytesMap>, +} + +impl InMemoryReadTransaction { + pub fn new(store: &InMemoryEnvironment) -> Result { + let view = { + let db_ref = Arc::clone(&store.data); + let view_lock = db_ref.lock()?; + view_lock.to_owned() + }; + Ok(InMemoryReadTransaction { view }) + } +} + +impl Transaction for InMemoryReadTransaction { + type Error = Error; + + type Handle = Option; + + fn commit(self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl Readable for InMemoryReadTransaction { + fn read(&self, handle: Self::Handle, key: &[u8]) -> Result>, Self::Error> { + let sub_view = match self.view.get(&handle) { + Some(view) => view, + None => return Ok(None), + }; + Ok(sub_view.get(&key.to_vec()).cloned()) + } +} + +/// A read-write transaction for the in-memory trie store. +pub struct InMemoryReadWriteTransaction<'a> { + view: HashMap, BytesMap>, + store_ref: Arc, BytesMap>>>, + _write_lock: WriteLock<'a>, +} + +impl<'a> InMemoryReadWriteTransaction<'a> { + pub fn new(store: &'a InMemoryEnvironment) -> Result, Error> { + let store_ref = Arc::clone(&store.data); + let view = { + let view_lock = store_ref.lock()?; + view_lock.to_owned() + }; + let _write_lock = store.write_mutex.lock()?; + Ok(InMemoryReadWriteTransaction { + view, + store_ref, + _write_lock, + }) + } +} + +impl<'a> Transaction for InMemoryReadWriteTransaction<'a> { + type Error = Error; + + type Handle = Option; + + fn commit(self) -> Result<(), Self::Error> { + let mut store_ref_lock = self.store_ref.lock()?; + store_ref_lock.extend(self.view); + Ok(()) + } +} + +impl<'a> Readable for InMemoryReadWriteTransaction<'a> { + fn read(&self, handle: Self::Handle, key: &[u8]) -> Result>, Self::Error> { + let sub_view = match self.view.get(&handle) { + Some(view) => view, + None => return Ok(None), + }; + Ok(sub_view.get(&key.to_vec()).cloned()) + } +} + +impl<'a> Writable for InMemoryReadWriteTransaction<'a> { + fn write(&mut self, handle: Self::Handle, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + let sub_view = self.view.entry(handle).or_default(); + sub_view.insert(key.to_vec(), value.to_vec()); + Ok(()) + } +} + +/// An environment for the in-memory trie store. +pub struct InMemoryEnvironment { + data: Arc, BytesMap>>>, + write_mutex: Arc>, +} + +impl Default for InMemoryEnvironment { + fn default() -> Self { + let data = { + let mut initial_map = HashMap::new(); + initial_map.insert(None, Default::default()); + Arc::new(Mutex::new(initial_map)) + }; + let write_mutex = Arc::new(Mutex::new(WriteCapability)); + InMemoryEnvironment { data, write_mutex } + } +} + +impl InMemoryEnvironment { + pub fn new() -> Self { + Default::default() + } + + pub fn data(&self, name: Option<&str>) -> Result, PoisonError> { + let data = self.data.lock()?; + let name = name.map(ToString::to_string); + let ret = data.get(&name).cloned(); + Ok(ret) + } +} + +impl<'a> TransactionSource<'a> for InMemoryEnvironment { + type Error = Error; + + type Handle = Option; + + type ReadTransaction = InMemoryReadTransaction; + + type ReadWriteTransaction = InMemoryReadWriteTransaction<'a>; + + fn create_read_txn(&'a self) -> Result { + InMemoryReadTransaction::new(self).map_err(Into::into) + } + + fn create_read_write_txn(&'a self) -> Result, Self::Error> { + InMemoryReadWriteTransaction::new(self).map_err(Into::into) + } +} diff --git a/node/src/contract_storage/transaction_source/lmdb.rs b/node/src/contract_storage/transaction_source/lmdb.rs new file mode 100644 index 0000000000..563587a6e6 --- /dev/null +++ b/node/src/contract_storage/transaction_source/lmdb.rs @@ -0,0 +1,102 @@ +use std::path::PathBuf; + +use lmdb::{self, Database, Environment, RoTransaction, RwTransaction, WriteFlags}; + +use crate::contract_storage::{ + error, + transaction_source::{Readable, Transaction, TransactionSource, Writable}, + MAX_DBS, +}; + +impl<'a> Transaction for RoTransaction<'a> { + type Error = lmdb::Error; + + type Handle = Database; + + fn commit(self) -> Result<(), Self::Error> { + lmdb::Transaction::commit(self) + } +} + +impl<'a> Readable for RoTransaction<'a> { + fn read(&self, handle: Self::Handle, key: &[u8]) -> Result>, Self::Error> { + match lmdb::Transaction::get(self, handle, &key) { + Ok(bytes) => Ok(Some(bytes.to_vec())), + Err(lmdb::Error::NotFound) => Ok(None), + Err(e) => Err(e), + } + } +} + +impl<'a> Transaction for RwTransaction<'a> { + type Error = lmdb::Error; + + type Handle = Database; + + fn commit(self) -> Result<(), Self::Error> { + as lmdb::Transaction>::commit(self) + } +} + +impl<'a> Readable for RwTransaction<'a> { + fn read(&self, handle: Self::Handle, key: &[u8]) -> Result>, Self::Error> { + match lmdb::Transaction::get(self, handle, &key) { + Ok(bytes) => Ok(Some(bytes.to_vec())), + Err(lmdb::Error::NotFound) => Ok(None), + Err(e) => Err(e), + } + } +} + +impl<'a> Writable for RwTransaction<'a> { + fn write(&mut self, handle: Self::Handle, key: &[u8], value: &[u8]) -> Result<(), Self::Error> { + self.put(handle, &key, &value, WriteFlags::empty()) + .map_err(Into::into) + } +} + +/// The environment for an LMDB-backed trie store. +/// +/// Wraps [`lmdb::Environment`]. +#[derive(Debug)] +pub struct LmdbEnvironment { + path: PathBuf, + env: Environment, +} + +impl LmdbEnvironment { + pub fn new(path: &PathBuf, map_size: usize) -> Result { + let env = Environment::new() + .set_max_dbs(MAX_DBS) + .set_map_size(map_size) + .open(path)?; + let path = path.to_owned(); + Ok(LmdbEnvironment { path, env }) + } + + pub fn path(&self) -> &PathBuf { + &self.path + } + + pub fn env(&self) -> &Environment { + &self.env + } +} + +impl<'a> TransactionSource<'a> for LmdbEnvironment { + type Error = lmdb::Error; + + type Handle = Database; + + type ReadTransaction = RoTransaction<'a>; + + type ReadWriteTransaction = RwTransaction<'a>; + + fn create_read_txn(&'a self) -> Result, Self::Error> { + self.env.begin_ro_txn() + } + + fn create_read_write_txn(&'a self) -> Result, Self::Error> { + self.env.begin_rw_txn() + } +} diff --git a/node/src/contract_storage/transaction_source/mod.rs b/node/src/contract_storage/transaction_source/mod.rs new file mode 100644 index 0000000000..547178d467 --- /dev/null +++ b/node/src/contract_storage/transaction_source/mod.rs @@ -0,0 +1,58 @@ +pub mod in_memory; +pub mod lmdb; + +/// A transaction which can be committed or aborted. +pub trait Transaction: Sized { + /// An error which can occur while reading or writing during a transaction, + /// or committing the transaction. + type Error; + + /// An entity which is being read from or written to during a transaction. + type Handle; + + /// Commits the transaction. + fn commit(self) -> Result<(), Self::Error>; + + /// Aborts the transaction. + /// + /// Any pending operations will not be saved. + fn abort(self) { + unimplemented!("Abort operations should be performed in Drop implementations.") + } +} + +/// A transaction with the capability to read from a given [`Handle`](Transaction::Handle). +pub trait Readable: Transaction { + /// Returns the value from the corresponding key from a given [`Transaction::Handle`]. + fn read(&self, handle: Self::Handle, key: &[u8]) -> Result>, Self::Error>; +} + +/// A transaction with the capability to write to a given [`Handle`](Transaction::Handle). +pub trait Writable: Transaction { + /// Inserts a key-value pair into a given [`Transaction::Handle`]. + fn write(&mut self, handle: Self::Handle, key: &[u8], value: &[u8]) -> Result<(), Self::Error>; +} + +/// A source of transactions e.g. values that implement [`Readable`] +/// and/or [`Writable`]. +pub trait TransactionSource<'a> { + /// An error which can occur while creating a read or read-write + /// transaction. + type Error; + + /// An entity which is being read from or written to during a transaction. + type Handle; + + /// Represents the type of read transactions. + type ReadTransaction: Readable; + + /// Represents the type of read-write transactions. + type ReadWriteTransaction: Readable + + Writable; + + /// Creates a read transaction. + fn create_read_txn(&'a self) -> Result; + + /// Creates a read-write transaction. + fn create_read_write_txn(&'a self) -> Result; +} diff --git a/node/src/contract_storage/trie/gens.rs b/node/src/contract_storage/trie/gens.rs new file mode 100644 index 0000000000..a4f5c7aedc --- /dev/null +++ b/node/src/contract_storage/trie/gens.rs @@ -0,0 +1,39 @@ +use proptest::{collection::vec, option, prelude::*}; + +use crate::contract_shared::{ + newtypes::Blake2bHash, + stored_value::{gens::stored_value_arb, StoredValue}, +}; +use types::{gens::key_arb, Key}; + +use super::{Pointer, PointerBlock, Trie}; + +pub fn blake2b_hash_arb() -> impl Strategy { + vec(any::(), 0..1000).prop_map(|b| Blake2bHash::new(&b)) +} + +pub fn trie_pointer_arb() -> impl Strategy { + prop_oneof![ + blake2b_hash_arb().prop_map(Pointer::LeafPointer), + blake2b_hash_arb().prop_map(Pointer::NodePointer) + ] +} + +pub fn trie_pointer_block_arb() -> impl Strategy { + vec(option::of(trie_pointer_arb()), 256).prop_map(|vec| { + let mut ret: [Option; 256] = [Default::default(); 256]; + ret.clone_from_slice(vec.as_slice()); + ret.into() + }) +} + +pub fn trie_arb() -> impl Strategy> { + prop_oneof![ + (key_arb(), stored_value_arb()).prop_map(|(key, value)| Trie::Leaf { key, value }), + trie_pointer_block_arb().prop_map(|pointer_block| Trie::Node { + pointer_block: Box::new(pointer_block) + }), + (vec(any::(), 0..32), trie_pointer_arb()) + .prop_map(|(affix, pointer)| Trie::Extension { affix, pointer }) + ] +} diff --git a/node/src/contract_storage/trie/mod.rs b/node/src/contract_storage/trie/mod.rs new file mode 100644 index 0000000000..b60a0bb4f1 --- /dev/null +++ b/node/src/contract_storage/trie/mod.rs @@ -0,0 +1,328 @@ +//! Core types for a Merkle Trie + +use crate::contract_shared::newtypes::{Blake2bHash, BLAKE2B_DIGEST_LENGTH}; +use types::bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}; + +#[cfg(test)] +pub mod gens; + +#[cfg(test)] +mod tests; + +pub const RADIX: usize = 256; + +/// A parent is represented as a pair of a child index and a node or extension. +pub type Parents = Vec<(u8, Trie)>; + +/// Represents a pointer to the next object in a Merkle Trie +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Pointer { + LeafPointer(Blake2bHash), + NodePointer(Blake2bHash), +} + +impl Pointer { + pub fn hash(&self) -> &Blake2bHash { + match self { + Pointer::LeafPointer(hash) => hash, + Pointer::NodePointer(hash) => hash, + } + } + + pub fn update(&self, hash: Blake2bHash) -> Self { + match self { + Pointer::LeafPointer(_) => Pointer::LeafPointer(hash), + Pointer::NodePointer(_) => Pointer::NodePointer(hash), + } + } + + fn tag(&self) -> u8 { + match self { + Pointer::LeafPointer(_) => 0, + Pointer::NodePointer(_) => 1, + } + } +} + +impl ToBytes for Pointer { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut ret = bytesrepr::unchecked_allocate_buffer(self); + ret.push(self.tag()); + + let mut hash_bytes = self.hash().to_bytes()?; + ret.append(&mut hash_bytes); + + Ok(ret) + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + BLAKE2B_DIGEST_LENGTH + } +} + +impl FromBytes for Pointer { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, rem) = u8::from_bytes(bytes)?; + match tag { + 0 => { + let (hash, rem) = Blake2bHash::from_bytes(rem)?; + Ok((Pointer::LeafPointer(hash), rem)) + } + 1 => { + let (hash, rem) = Blake2bHash::from_bytes(rem)?; + Ok((Pointer::NodePointer(hash), rem)) + } + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +/// Represents the underlying structure of a node in a Merkle Trie +#[derive(Copy, Clone)] +pub struct PointerBlock([Option; RADIX]); + +impl PointerBlock { + pub fn new() -> Self { + Default::default() + } + + pub fn from_indexed_pointers(indexed_pointers: &[(usize, Pointer)]) -> Self { + let mut ret = PointerBlock::new(); + for (idx, ptr) in indexed_pointers.iter() { + ret[*idx] = Some(*ptr); + } + ret + } +} + +impl From<[Option; RADIX]> for PointerBlock { + fn from(src: [Option; RADIX]) -> Self { + PointerBlock(src) + } +} + +impl PartialEq for PointerBlock { + #[inline] + fn eq(&self, other: &PointerBlock) -> bool { + self.0[..] == other.0[..] + } +} + +impl Eq for PointerBlock {} + +impl Default for PointerBlock { + fn default() -> Self { + PointerBlock([Default::default(); RADIX]) + } +} + +impl ToBytes for PointerBlock { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } +} + +impl FromBytes for PointerBlock { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + FromBytes::from_bytes(bytes).map(|(arr, rem)| (PointerBlock(arr), rem)) + } +} + +impl core::ops::Index for PointerBlock { + type Output = Option; + + #[inline] + fn index(&self, index: usize) -> &Self::Output { + let PointerBlock(dat) = self; + &dat[index] + } +} + +impl core::ops::IndexMut for PointerBlock { + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + let PointerBlock(dat) = self; + &mut dat[index] + } +} + +impl core::ops::Index> for PointerBlock { + type Output = [Option]; + + #[inline] + fn index(&self, index: core::ops::Range) -> &[Option] { + let &PointerBlock(ref dat) = self; + &dat[index] + } +} + +impl core::ops::Index> for PointerBlock { + type Output = [Option]; + + #[inline] + fn index(&self, index: core::ops::RangeTo) -> &[Option] { + let &PointerBlock(ref dat) = self; + &dat[index] + } +} + +impl core::ops::Index> for PointerBlock { + type Output = [Option]; + + #[inline] + fn index(&self, index: core::ops::RangeFrom) -> &[Option] { + let &PointerBlock(ref dat) = self; + &dat[index] + } +} + +impl core::ops::Index for PointerBlock { + type Output = [Option]; + + #[inline] + fn index(&self, index: core::ops::RangeFull) -> &[Option] { + let &PointerBlock(ref dat) = self; + &dat[index] + } +} + +impl ::std::fmt::Debug for PointerBlock { + #[allow(clippy::assertions_on_constants)] + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + assert!(RADIX > 1, "RADIX must be > 1"); + write!(f, "{}([", stringify!(PointerBlock))?; + write!(f, "{:?}", self.0[0])?; + for item in self.0[1..].iter() { + write!(f, ", {:?}", item)?; + } + write!(f, "])") + } +} + +/// Represents a Merkle Trie +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Trie { + Leaf { key: K, value: V }, + Node { pointer_block: Box }, + Extension { affix: Vec, pointer: Pointer }, +} + +impl Trie { + fn tag(&self) -> u8 { + match self { + Trie::Leaf { .. } => 0, + Trie::Node { .. } => 1, + Trie::Extension { .. } => 2, + } + } + + /// Constructs a [`Trie::Leaf`] from a given key and value. + pub fn leaf(key: K, value: V) -> Self { + Trie::Leaf { key, value } + } + + /// Constructs a [`Trie::Node`] from a given slice of indexed pointers. + pub fn node(indexed_pointers: &[(usize, Pointer)]) -> Self { + let pointer_block = PointerBlock::from_indexed_pointers(indexed_pointers); + let pointer_block = Box::new(pointer_block); + Trie::Node { pointer_block } + } + + /// Constructs a [`Trie::Extension`] from a given affix and pointer. + pub fn extension(affix: Vec, pointer: Pointer) -> Self { + Trie::Extension { affix, pointer } + } + + pub fn key(&self) -> Option<&K> { + match self { + Trie::Leaf { key, .. } => Some(key), + _ => None, + } + } +} + +impl ToBytes for Trie +where + K: ToBytes, + V: ToBytes, +{ + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut ret = bytesrepr::allocate_buffer(self)?; + ret.push(self.tag()); + + match self { + Trie::Leaf { key, value } => { + ret.append(&mut key.to_bytes()?); + ret.append(&mut value.to_bytes()?); + } + Trie::Node { pointer_block } => { + ret.append(&mut pointer_block.to_bytes()?); + } + Trie::Extension { affix, pointer } => { + ret.append(&mut affix.to_bytes()?); + ret.append(&mut pointer.to_bytes()?); + } + } + Ok(ret) + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + + match self { + Trie::Leaf { key, value } => key.serialized_length() + value.serialized_length(), + Trie::Node { pointer_block } => pointer_block.serialized_length(), + Trie::Extension { affix, pointer } => { + affix.serialized_length() + pointer.serialized_length() + } + } + } +} + +impl FromBytes for Trie { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, rem) = u8::from_bytes(bytes)?; + match tag { + 0 => { + let (key, rem) = K::from_bytes(rem)?; + let (value, rem) = V::from_bytes(rem)?; + Ok((Trie::Leaf { key, value }, rem)) + } + 1 => { + let (pointer_block, rem) = PointerBlock::from_bytes(rem)?; + Ok(( + Trie::Node { + pointer_block: Box::new(pointer_block), + }, + rem, + )) + } + 2 => { + let (affix, rem) = Vec::::from_bytes(rem)?; + let (pointer, rem) = Pointer::from_bytes(rem)?; + Ok((Trie::Extension { affix, pointer }, rem)) + } + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +pub(crate) mod operations { + use crate::contract_shared::newtypes::Blake2bHash; + use crate::contract_storage::trie::Trie; + use types::bytesrepr::{self, ToBytes}; + + /// Creates a tuple containing an empty root hash and an empty root (a node + /// with an empty pointer block) + pub fn create_hashed_empty_trie( + ) -> Result<(Blake2bHash, Trie), bytesrepr::Error> { + let root: Trie = Trie::Node { + pointer_block: Default::default(), + }; + let root_bytes: Vec = root.to_bytes()?; + Ok((Blake2bHash::new(&root_bytes), root)) + } +} diff --git a/node/src/contract_storage/trie/tests.rs b/node/src/contract_storage/trie/tests.rs new file mode 100644 index 0000000000..4ab537e00e --- /dev/null +++ b/node/src/contract_storage/trie/tests.rs @@ -0,0 +1,79 @@ +#[test] +fn radix_is_256() { + assert_eq!( + super::RADIX, + 256, + "Changing RADIX alone might cause things to break" + ); +} + +mod pointer_block { + use crate::contract_shared::newtypes::Blake2bHash; + + use crate::contract_storage::trie::*; + + /// A defense against changes to [`RADIX`](history::trie::RADIX). + #[test] + fn debug_formatter_succeeds() { + let _ = format!("{:?}", PointerBlock::new()); + } + + #[test] + fn assignment_and_indexing() { + let test_hash = Blake2bHash::new(b"TrieTrieAgain"); + let leaf_pointer = Some(Pointer::LeafPointer(test_hash)); + let mut pointer_block = PointerBlock::new(); + pointer_block[0] = leaf_pointer; + pointer_block[RADIX - 1] = leaf_pointer; + assert_eq!(leaf_pointer, pointer_block[0]); + assert_eq!(leaf_pointer, pointer_block[RADIX - 1]); + assert_eq!(None, pointer_block[1]); + assert_eq!(None, pointer_block[RADIX - 2]); + } + + #[test] + #[should_panic] + fn assignment_off_end() { + let test_hash = Blake2bHash::new(b"TrieTrieAgain"); + let leaf_pointer = Some(Pointer::LeafPointer(test_hash)); + let mut pointer_block = PointerBlock::new(); + pointer_block[RADIX] = leaf_pointer; + } + + #[test] + #[should_panic] + fn indexing_off_end() { + let pointer_block = PointerBlock::new(); + let _val = pointer_block[RADIX]; + } +} + +mod proptests { + use proptest::prelude::proptest; + + use types::bytesrepr; + + use crate::contract_storage::trie::gens::*; + + proptest! { + #[test] + fn roundtrip_blake2b_hash(hash in blake2b_hash_arb()) { + bytesrepr::test_serialization_roundtrip(&hash); + } + + #[test] + fn roundtrip_trie_pointer(pointer in trie_pointer_arb()) { + bytesrepr::test_serialization_roundtrip(&pointer); + } + + #[test] + fn roundtrip_trie_pointer_block(pointer_block in trie_pointer_block_arb()) { + bytesrepr::test_serialization_roundtrip(&pointer_block); + } + + #[test] + fn roundtrip_trie(trie in trie_arb()) { + bytesrepr::test_serialization_roundtrip(&trie); + } + } +} diff --git a/node/src/contract_storage/trie_store/in_memory.rs b/node/src/contract_storage/trie_store/in_memory.rs new file mode 100644 index 0000000000..6bdfaf0deb --- /dev/null +++ b/node/src/contract_storage/trie_store/in_memory.rs @@ -0,0 +1,131 @@ +//! An in-memory trie store, intended to be used for testing. +//! +//! # Usage +//! +//! ``` +//! use casperlabs_node::contract_storage::store::Store; +//! use casperlabs_node::contract_storage::transaction_source::{Transaction, TransactionSource}; +//! use casperlabs_node::contract_storage::transaction_source::in_memory::InMemoryEnvironment; +//! use casperlabs_node::contract_storage::trie::{Pointer, PointerBlock, Trie}; +//! use casperlabs_node::contract_storage::trie_store::in_memory::InMemoryTrieStore; +//! use types::bytesrepr::ToBytes; +//! use casperlabs_node::contract_shared::newtypes::Blake2bHash; +//! +//! // Create some leaves +//! let leaf_1 = Trie::Leaf { key: vec![0u8, 0, 0], value: b"val_1".to_vec() }; +//! let leaf_2 = Trie::Leaf { key: vec![1u8, 0, 0], value: b"val_2".to_vec() }; +//! +//! // Get their hashes +//! let leaf_1_hash = Blake2bHash::new(&leaf_1.to_bytes().unwrap()); +//! let leaf_2_hash = Blake2bHash::new(&leaf_2.to_bytes().unwrap()); +//! +//! // Create a node +//! let node: Trie, Vec> = { +//! let mut pointer_block = PointerBlock::new(); +//! pointer_block[0] = Some(Pointer::LeafPointer(leaf_1_hash)); +//! pointer_block[1] = Some(Pointer::LeafPointer(leaf_2_hash)); +//! let pointer_block = Box::new(pointer_block); +//! Trie::Node { pointer_block } +//! }; +//! +//! // Get its hash +//! let node_hash = Blake2bHash::new(&node.to_bytes().unwrap()); +//! +//! // Create the environment and the store. For both the in-memory and +//! // LMDB-backed implementations, the environment is the source of +//! // transactions. +//! let env = InMemoryEnvironment::new(); +//! let store = InMemoryTrieStore::new(&env, None); +//! +//! // First let's create a read-write transaction, persist the values, but +//! // forget to commit the transaction. +//! { +//! // Create a read-write transaction +//! let mut txn = env.create_read_write_txn().unwrap(); +//! +//! // Put the values in the store +//! store.put(&mut txn, &leaf_1_hash, &leaf_1).unwrap(); +//! store.put(&mut txn, &leaf_2_hash, &leaf_2).unwrap(); +//! store.put(&mut txn, &node_hash, &node).unwrap(); +//! +//! // Here we forget to commit the transaction before it goes out of scope +//! } +//! +//! // Now let's check to see if the values were stored +//! { +//! // Create a read transaction +//! let txn = env.create_read_txn().unwrap(); +//! +//! // Observe that nothing has been persisted to the store +//! for hash in vec![&leaf_1_hash, &leaf_2_hash, &node_hash].iter() { +//! // We need to use a type annotation here to help the compiler choose +//! // a suitable FromBytes instance +//! let maybe_trie: Option, Vec>> = store.get(&txn, hash).unwrap(); +//! assert!(maybe_trie.is_none()); +//! } +//! +//! // Commit the read transaction. Not strictly necessary, but better to be hygienic. +//! txn.commit().unwrap(); +//! } +//! +//! // Now let's try that again, remembering to commit the transaction this time +//! { +//! // Create a read-write transaction +//! let mut txn = env.create_read_write_txn().unwrap(); +//! +//! // Put the values in the store +//! store.put(&mut txn, &leaf_1_hash, &leaf_1).unwrap(); +//! store.put(&mut txn, &leaf_2_hash, &leaf_2).unwrap(); +//! store.put(&mut txn, &node_hash, &node).unwrap(); +//! +//! // Commit the transaction. +//! txn.commit().unwrap(); +//! } +//! +//! // Now let's check to see if the values were stored again +//! { +//! // Create a read transaction +//! let txn = env.create_read_txn().unwrap(); +//! +//! // Get the values in the store +//! assert_eq!(Some(leaf_1), store.get(&txn, &leaf_1_hash).unwrap()); +//! assert_eq!(Some(leaf_2), store.get(&txn, &leaf_2_hash).unwrap()); +//! assert_eq!(Some(node), store.get(&txn, &node_hash).unwrap()); +//! +//! // Commit the read transaction. +//! txn.commit().unwrap(); +//! } +//! ``` + +use super::{Blake2bHash, Store, Trie, TrieStore, NAME}; +use crate::contract_storage::{ + error::in_memory::Error, transaction_source::in_memory::InMemoryEnvironment, +}; + +/// An in-memory trie store. +pub struct InMemoryTrieStore { + maybe_name: Option, +} + +impl InMemoryTrieStore { + pub fn new(_env: &InMemoryEnvironment, maybe_name: Option<&str>) -> Self { + let name = maybe_name + .map(|name| format!("{}-{}", NAME, name)) + .unwrap_or_else(|| String::from(NAME)); + InMemoryTrieStore { + maybe_name: Some(name), + } + } +} + +impl Store> for InMemoryTrieStore { + type Error = Error; + + type Handle = Option; + + fn handle(&self) -> Self::Handle { + self.maybe_name.to_owned() + } +} + +impl TrieStore for InMemoryTrieStore {} diff --git a/node/src/contract_storage/trie_store/lmdb.rs b/node/src/contract_storage/trie_store/lmdb.rs new file mode 100644 index 0000000000..3b0a161061 --- /dev/null +++ b/node/src/contract_storage/trie_store/lmdb.rs @@ -0,0 +1,160 @@ +//! An LMDB-backed trie store. +//! +//! # Usage +//! +//! ``` +//! use casperlabs_node::contract_storage::store::Store; +//! use casperlabs_node::contract_storage::transaction_source::{Transaction, TransactionSource}; +//! use casperlabs_node::contract_storage::transaction_source::lmdb::LmdbEnvironment; +//! use casperlabs_node::contract_storage::trie::{Pointer, PointerBlock, Trie}; +//! use casperlabs_node::contract_storage::trie_store::lmdb::LmdbTrieStore; +//! use casperlabs_node::contract_shared::newtypes::Blake2bHash; +//! use types::bytesrepr::ToBytes; +//! use lmdb::DatabaseFlags; +//! use tempfile::tempdir; +//! +//! // Create some leaves +//! let leaf_1 = Trie::Leaf { key: vec![0u8, 0, 0], value: b"val_1".to_vec() }; +//! let leaf_2 = Trie::Leaf { key: vec![1u8, 0, 0], value: b"val_2".to_vec() }; +//! +//! // Get their hashes +//! let leaf_1_hash = Blake2bHash::new(&leaf_1.to_bytes().unwrap()); +//! let leaf_2_hash = Blake2bHash::new(&leaf_2.to_bytes().unwrap()); +//! +//! // Create a node +//! let node: Trie, Vec> = { +//! let mut pointer_block = PointerBlock::new(); +//! pointer_block[0] = Some(Pointer::LeafPointer(leaf_1_hash)); +//! pointer_block[1] = Some(Pointer::LeafPointer(leaf_2_hash)); +//! let pointer_block = Box::new(pointer_block); +//! Trie::Node { pointer_block } +//! }; +//! +//! // Get its hash +//! let node_hash = Blake2bHash::new(&node.to_bytes().unwrap()); +//! +//! // Create the environment and the store. For both the in-memory and +//! // LMDB-backed implementations, the environment is the source of +//! // transactions. +//! let tmp_dir = tempdir().unwrap(); +//! let map_size = 4096 * 2560; // map size should be a multiple of OS page size +//! let env = LmdbEnvironment::new(&tmp_dir.path().to_path_buf(), map_size).unwrap(); +//! let store = LmdbTrieStore::new(&env, None, DatabaseFlags::empty()).unwrap(); +//! +//! // First let's create a read-write transaction, persist the values, but +//! // forget to commit the transaction. +//! { +//! // Create a read-write transaction +//! let mut txn = env.create_read_write_txn().unwrap(); +//! +//! // Put the values in the store +//! store.put(&mut txn, &leaf_1_hash, &leaf_1).unwrap(); +//! store.put(&mut txn, &leaf_2_hash, &leaf_2).unwrap(); +//! store.put(&mut txn, &node_hash, &node).unwrap(); +//! +//! // Here we forget to commit the transaction before it goes out of scope +//! } +//! +//! // Now let's check to see if the values were stored +//! { +//! // Create a read transaction +//! let txn = env.create_read_txn().unwrap(); +//! +//! // Observe that nothing has been persisted to the store +//! for hash in vec![&leaf_1_hash, &leaf_2_hash, &node_hash].iter() { +//! // We need to use a type annotation here to help the compiler choose +//! // a suitable FromBytes instance +//! let maybe_trie: Option, Vec>> = store.get(&txn, hash).unwrap(); +//! assert!(maybe_trie.is_none()); +//! } +//! +//! // Commit the read transaction. Not strictly necessary, but better to be hygienic. +//! txn.commit().unwrap(); +//! } +//! +//! // Now let's try that again, remembering to commit the transaction this time +//! { +//! // Create a read-write transaction +//! let mut txn = env.create_read_write_txn().unwrap(); +//! +//! // Put the values in the store +//! store.put(&mut txn, &leaf_1_hash, &leaf_1).unwrap(); +//! store.put(&mut txn, &leaf_2_hash, &leaf_2).unwrap(); +//! store.put(&mut txn, &node_hash, &node).unwrap(); +//! +//! // Commit the transaction. +//! txn.commit().unwrap(); +//! } +//! +//! // Now let's check to see if the values were stored again +//! { +//! // Create a read transaction +//! let txn = env.create_read_txn().unwrap(); +//! +//! // Get the values in the store +//! assert_eq!(Some(leaf_1), store.get(&txn, &leaf_1_hash).unwrap()); +//! assert_eq!(Some(leaf_2), store.get(&txn, &leaf_2_hash).unwrap()); +//! assert_eq!(Some(node), store.get(&txn, &node_hash).unwrap()); +//! +//! // Commit the read transaction. +//! txn.commit().unwrap(); +//! } +//! +//! tmp_dir.close().unwrap(); +//! ``` + +use lmdb::{Database, DatabaseFlags}; + +use crate::contract_shared::newtypes::Blake2bHash; + +use crate::contract_storage::{ + error, + store::Store, + transaction_source::lmdb::LmdbEnvironment, + trie::Trie, + trie_store::{self, TrieStore}, +}; + +/// An LMDB-backed trie store. +/// +/// Wraps [`lmdb::Database`]. +#[derive(Debug, Clone)] +pub struct LmdbTrieStore { + db: Database, +} + +impl LmdbTrieStore { + pub fn new( + env: &LmdbEnvironment, + maybe_name: Option<&str>, + flags: DatabaseFlags, + ) -> Result { + let name = Self::name(maybe_name); + let db = env.env().create_db(Some(&name), flags)?; + Ok(LmdbTrieStore { db }) + } + + pub fn open(env: &LmdbEnvironment, maybe_name: Option<&str>) -> Result { + let name = Self::name(maybe_name); + let db = env.env().open_db(Some(&name))?; + Ok(LmdbTrieStore { db }) + } + + fn name(maybe_name: Option<&str>) -> String { + maybe_name + .map(|name| format!("{}-{}", trie_store::NAME, name)) + .unwrap_or_else(|| String::from(trie_store::NAME)) + } +} + +impl Store> for LmdbTrieStore { + type Error = error::Error; + + type Handle = Database; + + fn handle(&self) -> Self::Handle { + self.db + } +} + +impl TrieStore for LmdbTrieStore {} diff --git a/node/src/contract_storage/trie_store/mod.rs b/node/src/contract_storage/trie_store/mod.rs new file mode 100644 index 0000000000..ba1cf1dc25 --- /dev/null +++ b/node/src/contract_storage/trie_store/mod.rs @@ -0,0 +1,18 @@ +//! A store for persisting [`Trie`](crate::trie::Trie) values at their hashes. +//! +//! See the [in_memory](in_memory/index.html#usage) and +//! [lmdb](lmdb/index.html#usage) modules for usage examples. +pub mod in_memory; +pub mod lmdb; +pub(crate) mod operations; +#[cfg(test)] +mod tests; + +use crate::contract_shared::newtypes::Blake2bHash; + +use crate::contract_storage::{store::Store, trie::Trie}; + +const NAME: &str = "TRIE_STORE"; + +/// An entity which persists [`Trie`] values at their hashes. +pub trait TrieStore: Store> {} diff --git a/node/src/contract_storage/trie_store/operations/mod.rs b/node/src/contract_storage/trie_store/operations/mod.rs new file mode 100644 index 0000000000..41d531b63b --- /dev/null +++ b/node/src/contract_storage/trie_store/operations/mod.rs @@ -0,0 +1,940 @@ +#[cfg(test)] +mod tests; + +use std::{cmp, collections::VecDeque, mem, time::Instant}; + +use crate::contract_shared::{ + logging::{log_duration, log_metric}, + newtypes::{Blake2bHash, CorrelationId}, +}; +use types::bytesrepr::{self, FromBytes, ToBytes}; + +use crate::contract_storage::{ + transaction_source::{Readable, Writable}, + trie::{Parents, Pointer, Trie, RADIX}, + trie_store::TrieStore, + GAUGE_METRIC_KEY, +}; + +const TRIE_STORE_READ_DURATION: &str = "trie_store_read_duration"; +const TRIE_STORE_READ_GETS: &str = "trie_store_read_gets"; +const TRIE_STORE_SCAN_DURATION: &str = "trie_store_scan_duration"; +const TRIE_STORE_SCAN_GETS: &str = "trie_store_scan_gets"; +const TRIE_STORE_WRITE_DURATION: &str = "trie_store_write_duration"; +const TRIE_STORE_WRITE_PUTS: &str = "trie_store_write_puts"; +const READ: &str = "read"; +const GET: &str = "get"; +const SCAN: &str = "scan"; +const WRITE: &str = "write"; +const PUT: &str = "put"; + +#[derive(Debug, PartialEq, Eq)] +pub enum ReadResult { + Found(V), + NotFound, + RootNotFound, +} + +/// Returns a value from the corresponding key at a given root in a given store +pub fn read( + correlation_id: CorrelationId, + txn: &T, + store: &S, + root: &Blake2bHash, + key: &K, +) -> Result, E> +where + K: ToBytes + FromBytes + Eq + std::fmt::Debug, + V: ToBytes + FromBytes, + T: Readable, + S: TrieStore, + S::Error: From, + E: From + From, +{ + let path: Vec = key.to_bytes()?; + + let mut depth: usize = 0; + let mut current: Trie = match store.get(txn, root)? { + Some(root) => root, + None => return Ok(ReadResult::RootNotFound), + }; + + let start = Instant::now(); + let mut get_counter: i32 = 0; + + loop { + match current { + Trie::Leaf { + key: leaf_key, + value: leaf_value, + } => { + let result = if *key == leaf_key { + ReadResult::Found(leaf_value) + } else { + // Keys may not match in the case of a compressed path from + // a Node directly to a Leaf + ReadResult::NotFound + }; + log_metric( + correlation_id, + TRIE_STORE_READ_GETS, + GET, + GAUGE_METRIC_KEY, + f64::from(get_counter), + ); + log_duration( + correlation_id, + TRIE_STORE_READ_DURATION, + READ, + start.elapsed(), + ); + return Ok(result); + } + Trie::Node { pointer_block } => { + let index: usize = { + assert!(depth < path.len(), "depth must be < {}", path.len()); + path[depth].into() + }; + let maybe_pointer: Option = { + assert!(index < RADIX, "key length must be < {}", RADIX); + pointer_block[index] + }; + match maybe_pointer { + Some(pointer) => match store.get(txn, pointer.hash())? { + Some(next) => { + get_counter += 1; + depth += 1; + current = next; + } + None => { + get_counter += 1; + log_metric( + correlation_id, + TRIE_STORE_READ_GETS, + GET, + GAUGE_METRIC_KEY, + f64::from(get_counter), + ); + log_duration( + correlation_id, + TRIE_STORE_READ_DURATION, + READ, + start.elapsed(), + ); + panic!( + "No trie value at key: {:?} (reading from key: {:?})", + pointer.hash(), + key + ); + } + }, + None => { + log_metric( + correlation_id, + TRIE_STORE_READ_GETS, + GET, + GAUGE_METRIC_KEY, + f64::from(get_counter), + ); + log_duration( + correlation_id, + TRIE_STORE_READ_DURATION, + READ, + start.elapsed(), + ); + return Ok(ReadResult::NotFound); + } + } + } + Trie::Extension { affix, pointer } => { + let sub_path = &path[depth..depth + affix.len()]; + if sub_path == affix.as_slice() { + get_counter += 1; + match store.get(txn, pointer.hash())? { + Some(next) => { + get_counter += 1; + depth += affix.len(); + current = next; + } + None => { + get_counter += 1; + log_metric( + correlation_id, + TRIE_STORE_READ_GETS, + GET, + GAUGE_METRIC_KEY, + f64::from(get_counter), + ); + log_duration( + correlation_id, + TRIE_STORE_READ_DURATION, + READ, + start.elapsed(), + ); + panic!( + "No trie value at key: {:?} (reading from key: {:?})", + pointer.hash(), + key + ); + } + } + } else { + log_metric( + correlation_id, + TRIE_STORE_READ_GETS, + GET, + GAUGE_METRIC_KEY, + f64::from(get_counter), + ); + log_duration( + correlation_id, + TRIE_STORE_READ_DURATION, + READ, + start.elapsed(), + ); + return Ok(ReadResult::NotFound); + } + } + } + } +} + +struct TrieScan { + tip: Trie, + parents: Parents, +} + +impl TrieScan { + fn new(tip: Trie, parents: Parents) -> Self { + TrieScan { tip, parents } + } +} + +/// Returns a [`TrieScan`] from the given key at a given root in a given store. +/// A scan consists of the deepest trie variant found at that key, a.k.a. the +/// "tip", along the with the parents of that variant. Parents are ordered by +/// their depth from the root (shallow to deep). +fn scan( + correlation_id: CorrelationId, + txn: &T, + store: &S, + key_bytes: &[u8], + root: &Trie, +) -> Result, E> +where + K: ToBytes + FromBytes + Clone, + V: ToBytes + FromBytes + Clone, + T: Readable, + S: TrieStore, + S::Error: From, + E: From + From, +{ + let start = Instant::now(); + let mut get_counter: i32 = 0; + + let path = key_bytes; + + let mut current = root.to_owned(); + let mut depth: usize = 0; + let mut acc: Parents = Vec::new(); + + loop { + match current { + leaf @ Trie::Leaf { .. } => { + log_metric( + correlation_id, + TRIE_STORE_SCAN_GETS, + GET, + GAUGE_METRIC_KEY, + f64::from(get_counter), + ); + log_duration( + correlation_id, + TRIE_STORE_SCAN_DURATION, + SCAN, + start.elapsed(), + ); + return Ok(TrieScan::new(leaf, acc)); + } + Trie::Node { pointer_block } => { + let index = { + assert!(depth < path.len(), "depth must be < {}", path.len()); + path[depth] + }; + let maybe_pointer: Option = { + let index: usize = index.into(); + assert!(index < RADIX, "index must be < {}", RADIX); + pointer_block[index] + }; + let pointer = match maybe_pointer { + Some(pointer) => pointer, + None => { + log_metric( + correlation_id, + TRIE_STORE_SCAN_GETS, + GET, + GAUGE_METRIC_KEY, + f64::from(get_counter), + ); + log_duration( + correlation_id, + TRIE_STORE_SCAN_DURATION, + SCAN, + start.elapsed(), + ); + return Ok(TrieScan::new(Trie::Node { pointer_block }, acc)); + } + }; + match store.get(txn, pointer.hash())? { + Some(next) => { + get_counter += 1; + current = next; + depth += 1; + acc.push((index, Trie::Node { pointer_block })) + } + None => { + get_counter += 1; + log_metric( + correlation_id, + TRIE_STORE_SCAN_GETS, + GET, + GAUGE_METRIC_KEY, + f64::from(get_counter), + ); + log_duration( + correlation_id, + TRIE_STORE_SCAN_DURATION, + SCAN, + start.elapsed(), + ); + panic!( + "No trie value at key: {:?} (reading from path: {:?})", + pointer.hash(), + path + ); + } + } + } + Trie::Extension { affix, pointer } => { + let sub_path = &path[depth..depth + affix.len()]; + if sub_path != affix.as_slice() { + log_metric( + correlation_id, + TRIE_STORE_SCAN_GETS, + GET, + GAUGE_METRIC_KEY, + f64::from(get_counter), + ); + log_duration( + correlation_id, + TRIE_STORE_SCAN_DURATION, + SCAN, + start.elapsed(), + ); + return Ok(TrieScan::new(Trie::Extension { affix, pointer }, acc)); + } + match store.get(txn, pointer.hash())? { + Some(next) => { + get_counter += 1; + let index = { + assert!(depth < path.len(), "depth must be < {}", path.len()); + path[depth] + }; + current = next; + depth += affix.len(); + acc.push((index, Trie::Extension { affix, pointer })) + } + None => { + get_counter += 1; + log_metric( + correlation_id, + TRIE_STORE_SCAN_GETS, + GET, + GAUGE_METRIC_KEY, + f64::from(get_counter), + ); + log_duration( + correlation_id, + TRIE_STORE_SCAN_DURATION, + SCAN, + start.elapsed(), + ); + panic!( + "No trie value at key: {:?} (reading from path: {:?})", + pointer.hash(), + path + ); + } + } + } + } + } +} + +#[allow(clippy::type_complexity)] +fn rehash( + mut tip: Trie, + parents: Parents, +) -> Result)>, bytesrepr::Error> +where + K: ToBytes + Clone, + V: ToBytes + Clone, +{ + let mut ret: Vec<(Blake2bHash, Trie)> = Vec::new(); + let mut tip_hash = { + let trie_bytes = tip.to_bytes()?; + Blake2bHash::new(&trie_bytes) + }; + ret.push((tip_hash, tip.to_owned())); + + for (index, parent) in parents.into_iter().rev() { + match parent { + Trie::Leaf { .. } => { + panic!("parents should not contain any leaves"); + } + Trie::Node { mut pointer_block } => { + tip = { + let pointer = match tip { + Trie::Leaf { .. } => Pointer::LeafPointer(tip_hash), + Trie::Node { .. } => Pointer::NodePointer(tip_hash), + Trie::Extension { .. } => Pointer::NodePointer(tip_hash), + }; + pointer_block[index.into()] = Some(pointer); + Trie::Node { pointer_block } + }; + tip_hash = { + let node_bytes = tip.to_bytes()?; + Blake2bHash::new(&node_bytes) + }; + ret.push((tip_hash, tip.to_owned())) + } + Trie::Extension { affix, pointer } => { + tip = { + let pointer = pointer.update(tip_hash); + Trie::Extension { affix, pointer } + }; + tip_hash = { + let extension_bytes = tip.to_bytes()?; + Blake2bHash::new(&extension_bytes) + }; + ret.push((tip_hash, tip.to_owned())) + } + } + } + Ok(ret) +} + +fn common_prefix(ls: &[A], rs: &[A]) -> Vec { + ls.iter() + .zip(rs.iter()) + .take_while(|(l, r)| l == r) + .map(|(l, _)| l.to_owned()) + .collect() +} + +fn get_parents_path(parents: &[(u8, Trie)]) -> Vec { + let mut ret = Vec::new(); + for (index, element) in parents.iter() { + if let Trie::Extension { affix, .. } = element { + ret.extend(affix); + } else { + ret.push(index.to_owned()); + } + } + ret +} + +/// Takes a path to a leaf, that leaf's parent node, and the parents of that +/// node, and adds the node to the parents. +/// +/// This function will panic if the the path to the leaf and the path to its +/// parent node do not share a common prefix. +fn add_node_to_parents( + path_to_leaf: &[u8], + new_parent_node: Trie, + mut parents: Parents, +) -> Result, bytesrepr::Error> +where + K: ToBytes, + V: ToBytes, +{ + // TODO: add is_node() method to Trie + match new_parent_node { + Trie::Node { .. } => (), + _ => panic!("new_parent must be a node"), + } + // The current depth will be the length of the path to the new parent node. + let depth: usize = { + // Get the path to this node + let path_to_node: Vec = get_parents_path(&parents); + // Check that the path to the node is a prefix of the current path + let current_path = common_prefix(&path_to_leaf, &path_to_node); + assert_eq!(current_path, path_to_node); + // Get the length + path_to_node.len() + }; + // Index path by current depth; + let index = { + assert!( + depth < path_to_leaf.len(), + "depth must be < {}", + path_to_leaf.len() + ); + path_to_leaf[depth] + }; + // Add node to parents, along with index to modify + parents.push((index, new_parent_node)); + Ok(parents) +} + +/// Takes paths to a new leaf and an existing leaf that share a common prefix, +/// along with the parents of the existing leaf. Creates a new node (adding a +/// possible parent extension for it to parents) which contains the existing +/// leaf. Returns the new node and parents, so that they can be used by +/// [`add_node_to_parents`]. +#[allow(clippy::type_complexity)] +fn reparent_leaf( + new_leaf_path: &[u8], + existing_leaf_path: &[u8], + parents: Parents, +) -> Result<(Trie, Parents), bytesrepr::Error> +where + K: ToBytes, + V: ToBytes, +{ + let mut parents = parents; + let (child_index, parent) = parents.pop().expect("parents should not be empty"); + let pointer_block = match parent { + Trie::Node { pointer_block } => pointer_block, + _ => panic!("A leaf should have a node for its parent"), + }; + // Get the path that the new leaf and existing leaf share + let shared_path = common_prefix(&new_leaf_path, &existing_leaf_path); + // Assemble a new node to hold the existing leaf. The new leaf will + // be added later during the add_parent_node and rehash phase. + let new_node = { + let index: usize = existing_leaf_path[shared_path.len()].into(); + let existing_leaf_pointer = + pointer_block[::from(child_index)].expect("parent has lost the existing leaf"); + Trie::node(&[(index, existing_leaf_pointer)]) + }; + // Re-add the parent node to parents + parents.push((child_index, Trie::Node { pointer_block })); + // Create an affix for a possible extension node + let affix = { + let parents_path = get_parents_path(&parents); + &shared_path[parents_path.len()..] + }; + // If the affix is non-empty, create an extension node and add it + // to parents. + if !affix.is_empty() { + let new_node_bytes = new_node.to_bytes()?; + let new_node_hash = Blake2bHash::new(&new_node_bytes); + let new_extension = Trie::extension(affix.to_vec(), Pointer::NodePointer(new_node_hash)); + parents.push((child_index, new_extension)); + } + Ok((new_node, parents)) +} + +struct SplitResult { + new_node: Trie, + parents: Parents, + maybe_hashed_child_extension: Option<(Blake2bHash, Trie)>, +} + +/// Takes a path to a new leaf, an existing extension that leaf collides with, +/// and the parents of that extension. Creates a new node and possible parent +/// and child extensions. The node pointer contained in the existing extension +/// is repositioned in the new node or the possible child extension. The +/// possible parent extension is added to parents. Returns the new node, +/// parents, and the the possible child extension (paired with its hash). +/// The new node and parents can be used by [`add_node_to_parents`], and the +/// new hashed child extension can be added to the list of new trie elements. +fn split_extension( + new_leaf_path: &[u8], + existing_extension: Trie, + mut parents: Parents, +) -> Result, bytesrepr::Error> +where + K: ToBytes + Clone, + V: ToBytes + Clone, +{ + // TODO: add is_extension() method to Trie + let (affix, pointer) = match existing_extension { + Trie::Extension { affix, pointer } => (affix, pointer), + _ => panic!("existing_extension must be an extension"), + }; + let parents_path = get_parents_path(&parents); + // Get the path to the existing extension node + let existing_extension_path: Vec = + parents_path.iter().chain(affix.iter()).cloned().collect(); + // Get the path that the new leaf and existing leaf share + let shared_path = common_prefix(&new_leaf_path, &existing_extension_path); + // Create an affix for a possible parent extension above the new + // node. + let parent_extension_affix = shared_path[parents_path.len()..].to_vec(); + // Create an affix for a possible child extension between the new + // node and the node that the existing extension pointed to. + let child_extension_affix = affix[parent_extension_affix.len() + 1..].to_vec(); + // Create a child extension (paired with its hash) if necessary + let maybe_hashed_child_extension: Option<(Blake2bHash, Trie)> = + if child_extension_affix.is_empty() { + None + } else { + let child_extension = Trie::extension(child_extension_affix.to_vec(), pointer); + let child_extension_bytes = child_extension.to_bytes()?; + let child_extension_hash = Blake2bHash::new(&child_extension_bytes); + Some((child_extension_hash, child_extension)) + }; + // Assemble a new node. + let new_node: Trie = { + let index: usize = existing_extension_path[shared_path.len()].into(); + let pointer = maybe_hashed_child_extension + .to_owned() + .map_or(pointer, |(hash, _)| Pointer::NodePointer(hash)); + Trie::node(&[(index, pointer)]) + }; + // Create a parent extension if necessary + if !parent_extension_affix.is_empty() { + let new_node_bytes = new_node.to_bytes()?; + let new_node_hash = Blake2bHash::new(&new_node_bytes); + let parent_extension = Trie::extension( + parent_extension_affix.to_vec(), + Pointer::NodePointer(new_node_hash), + ); + parents.push((parent_extension_affix[0], parent_extension)); + } + Ok(SplitResult { + new_node, + parents, + maybe_hashed_child_extension, + }) +} + +#[derive(Debug, PartialEq, Eq)] +pub enum WriteResult { + Written(Blake2bHash), + AlreadyExists, + RootNotFound, +} + +pub fn write( + correlation_id: CorrelationId, + txn: &mut T, + store: &S, + root: &Blake2bHash, + key: &K, + value: &V, +) -> Result +where + K: ToBytes + FromBytes + Clone + Eq + std::fmt::Debug, + V: ToBytes + FromBytes + Clone + Eq, + T: Readable + Writable, + S: TrieStore, + S::Error: From, + E: From + From, +{ + let start = Instant::now(); + let mut put_counter: i32 = 0; + + match store.get(txn, root)? { + None => Ok(WriteResult::RootNotFound), + Some(current_root) => { + let new_leaf = Trie::Leaf { + key: key.to_owned(), + value: value.to_owned(), + }; + let path: Vec = key.to_bytes()?; + let TrieScan { tip, parents } = + scan::(correlation_id, txn, store, &path, ¤t_root)?; + let new_elements: Vec<(Blake2bHash, Trie)> = match tip { + // If the "tip" is the same as the new leaf, then the leaf + // is already in the Trie. + Trie::Leaf { .. } if new_leaf == tip => Vec::new(), + // If the "tip" is an existing leaf with the same key as the + // new leaf, but the existing leaf and new leaf have different + // values, then we are in the situation where we are "updating" + // an existing leaf. + Trie::Leaf { + key: ref leaf_key, + value: ref leaf_value, + } if key == leaf_key && value != leaf_value => rehash(new_leaf, parents)?, + // If the "tip" is an existing leaf with a different key than + // the new leaf, then we are in a situation where the new leaf + // shares some common prefix with the existing leaf. + Trie::Leaf { + key: ref existing_leaf_key, + .. + } if key != existing_leaf_key => { + let existing_leaf_path = existing_leaf_key.to_bytes()?; + let (new_node, parents) = reparent_leaf(&path, &existing_leaf_path, parents)?; + let parents = add_node_to_parents(&path, new_node, parents)?; + rehash(new_leaf, parents)? + } + // This case is unreachable, but the compiler can't figure + // that out. + Trie::Leaf { .. } => unreachable!(), + // If the "tip" is an existing node, then we can add a pointer + // to the new leaf to the node's pointer block. + node @ Trie::Node { .. } => { + let parents = add_node_to_parents(&path, node, parents)?; + rehash(new_leaf, parents)? + } + // If the "tip" is an extension node, then we must modify or + // replace it, adding a node where necessary. + extension @ Trie::Extension { .. } => { + let SplitResult { + new_node, + parents, + maybe_hashed_child_extension, + } = split_extension(&path, extension, parents)?; + let parents = add_node_to_parents(&path, new_node, parents)?; + if let Some(hashed_extension) = maybe_hashed_child_extension { + let mut ret = vec![hashed_extension]; + ret.extend(rehash(new_leaf, parents)?); + ret + } else { + rehash(new_leaf, parents)? + } + } + }; + if new_elements.is_empty() { + log_duration( + correlation_id, + TRIE_STORE_WRITE_DURATION, + WRITE, + start.elapsed(), + ); + return Ok(WriteResult::AlreadyExists); + } + let mut root_hash = root.to_owned(); + for (hash, element) in new_elements.iter() { + put_counter += 1; + store.put(txn, hash, element)?; + root_hash = *hash; + } + log_metric( + correlation_id, + TRIE_STORE_WRITE_PUTS, + PUT, + GAUGE_METRIC_KEY, + f64::from(put_counter), + ); + log_duration( + correlation_id, + TRIE_STORE_WRITE_DURATION, + WRITE, + start.elapsed(), + ); + Ok(WriteResult::Written(root_hash)) + } + } +} + +enum KeysIteratorState> { + /// Iterate normally + Ok, + /// Return the error and stop iterating + ReturnError(S::Error), + /// Already failed, only return None + Failed, +} + +struct VisitedTrieNode { + trie: Trie, + maybe_index: Option, + path: Vec, +} + +pub struct KeysIterator<'a, 'b, K, V, T, S: TrieStore> { + initial_descend: VecDeque, + visited: Vec>, + store: &'a S, + txn: &'b T, + state: KeysIteratorState, +} + +impl<'a, 'b, K, V, T, S> Iterator for KeysIterator<'a, 'b, K, V, T, S> +where + K: ToBytes + FromBytes + Clone + Eq + std::fmt::Debug, + V: ToBytes + FromBytes + Clone + Eq + std::fmt::Debug, + T: Readable, + S: TrieStore, + S::Error: From + From, +{ + type Item = Result; + + fn next(&mut self) -> Option { + match mem::replace(&mut self.state, KeysIteratorState::Ok) { + KeysIteratorState::Ok => (), + KeysIteratorState::ReturnError(e) => { + self.state = KeysIteratorState::Failed; + return Some(Err(e)); + } + KeysIteratorState::Failed => { + return None; + } + } + while let Some(VisitedTrieNode { + trie, + maybe_index, + mut path, + }) = self.visited.pop() + { + let mut maybe_next_trie: Option> = None; + + match trie { + Trie::Leaf { key, .. } => { + let key_bytes = match key.to_bytes() { + Ok(bytes) => bytes, + Err(e) => { + self.state = KeysIteratorState::Failed; + return Some(Err(e.into())); + } + }; + debug_assert!(key_bytes.starts_with(&path)); + // only return the leaf if it matches the initial descend path + path.extend(&self.initial_descend); + if key_bytes.starts_with(&path) { + return Some(Ok(key)); + } + } + Trie::Node { ref pointer_block } => { + // if we are still initially descending (and initial_descend is not empty), take + // the first index we should descend to, otherwise take maybe_index from the + // visited stack + let mut index: usize = self + .initial_descend + .front() + .map(|i| *i as usize) + .or(maybe_index) + .unwrap_or_default(); + while index < RADIX { + if let Some(ref pointer) = pointer_block[index] { + maybe_next_trie = match self.store.get(self.txn, pointer.hash()) { + Ok(trie) => trie, + Err(e) => { + self.state = KeysIteratorState::Failed; + return Some(Err(e)); + } + }; + debug_assert!(maybe_next_trie.is_some()); + if self.initial_descend.pop_front().is_none() { + self.visited.push(VisitedTrieNode { + trie, + maybe_index: Some(index + 1), + path: path.clone(), + }); + } + path.push(index as u8); + break; + } + // only continue the loop if we are not initially descending; + // if we are descending and we land here, it means that there is no subtrie + // along the descend path and we will return no results + if !self.initial_descend.is_empty() { + break; + } + index += 1; + } + } + Trie::Extension { affix, pointer } => { + let descend_len = cmp::min(self.initial_descend.len(), affix.len()); + let check_prefix = self + .initial_descend + .drain(..descend_len) + .collect::>(); + // if we are initially descending, we only want to continue if the affix + // matches the descend path + // if we are not, the check_prefix will be empty, so we will enter the if + // anyway + if affix.starts_with(&check_prefix) { + maybe_next_trie = match self.store.get(self.txn, pointer.hash()) { + Ok(trie) => trie, + Err(e) => { + self.state = KeysIteratorState::Failed; + return Some(Err(e)); + } + }; + debug_assert!({ + match &maybe_next_trie { + Some(Trie::Node { .. }) => true, + _ => false, + } + }); + path.extend(affix); + } + } + } + + if let Some(next_trie) = maybe_next_trie { + self.visited.push(VisitedTrieNode { + trie: next_trie, + maybe_index: None, + path, + }); + } + } + None + } +} + +/// Returns the iterator over the keys at a given root hash. +/// +/// The root should be the apex of the trie. +#[allow(dead_code)] +pub fn keys<'a, 'b, K, V, T, S>( + correlation_id: CorrelationId, + txn: &'b T, + store: &'a S, + root: &Blake2bHash, +) -> KeysIterator<'a, 'b, K, V, T, S> +where + K: ToBytes + FromBytes + Clone + Eq + std::fmt::Debug, + V: ToBytes + FromBytes + Clone + Eq + std::fmt::Debug, + T: Readable, + S: TrieStore, + S::Error: From, +{ + keys_with_prefix(correlation_id, txn, store, root, &[]) +} + +/// Returns the iterator over the keys in the subtrie matching `prefix`. +/// +/// The root should be the apex of the trie. +#[allow(dead_code)] +pub fn keys_with_prefix<'a, 'b, K, V, T, S>( + _correlation_id: CorrelationId, + txn: &'b T, + store: &'a S, + root: &Blake2bHash, + prefix: &[u8], +) -> KeysIterator<'a, 'b, K, V, T, S> +where + K: ToBytes + FromBytes + Clone + Eq + std::fmt::Debug, + V: ToBytes + FromBytes + Clone + Eq + std::fmt::Debug, + T: Readable, + S: TrieStore, + S::Error: From, +{ + let (visited, init_state): (Vec>, _) = match store.get(txn, root) { + Ok(None) => (vec![], KeysIteratorState::Ok), + Err(e) => (vec![], KeysIteratorState::ReturnError(e)), + Ok(Some(current_root)) => ( + vec![VisitedTrieNode { + trie: current_root, + maybe_index: None, + path: vec![], + }], + KeysIteratorState::Ok, + ), + }; + + KeysIterator { + initial_descend: prefix.iter().cloned().collect(), + visited, + store, + txn, + state: init_state, + } +} diff --git a/node/src/contract_storage/trie_store/operations/tests/ee_699.rs b/node/src/contract_storage/trie_store/operations/tests/ee_699.rs new file mode 100644 index 0000000000..b60f39143c --- /dev/null +++ b/node/src/contract_storage/trie_store/operations/tests/ee_699.rs @@ -0,0 +1,410 @@ +use proptest::{arbitrary, array, collection, prop_oneof, strategy::Strategy}; + +use crate::contract_shared::{make_array_newtype, newtypes::Blake2bHash}; +use types::{ + bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + gens, URef, +}; + +use super::{HashedTrie, TestValue}; +use crate::trie::Trie; + +pub const BASIC_LENGTH: usize = 4; +pub const SIMILAR_LENGTH: usize = 4; +pub const FANCY_LENGTH: usize = 5; +pub const LONG_LENGTH: usize = 8; + +const PUBLIC_KEY_BASIC_ID: u8 = 0; +const PUBLIC_KEY_SIMILAR_ID: u8 = 1; +const PUBLIC_KEY_FANCY_ID: u8 = 2; +const PUBLIC_KEY_LONG_ID: u8 = 3; + +pub const KEY_HASH_LENGTH: usize = 32; + +const KEY_ACCOUNT_ID: u8 = 0; +const KEY_HASH_ID: u8 = 1; +const KEY_UREF_ID: u8 = 2; + +make_array_newtype!(Basic, u8, BASIC_LENGTH); +make_array_newtype!(Similar, u8, SIMILAR_LENGTH); +make_array_newtype!(Fancy, u8, FANCY_LENGTH); +make_array_newtype!(Long, u8, LONG_LENGTH); + +macro_rules! impl_distribution_for_array_newtype { + ($name:ident, $ty:ty, $len:expr) => { + impl rand::distributions::Distribution<$name> for rand::distributions::Standard { + fn sample(&self, rng: &mut R) -> $name { + let mut dat = [0u8; $len]; + rng.fill_bytes(dat.as_mut()); + $name(dat) + } + } + }; +} + +impl_distribution_for_array_newtype!(Basic, u8, BASIC_LENGTH); +impl_distribution_for_array_newtype!(Similar, u8, SIMILAR_LENGTH); +impl_distribution_for_array_newtype!(Fancy, u8, FANCY_LENGTH); +impl_distribution_for_array_newtype!(Long, u8, LONG_LENGTH); + +macro_rules! make_array_newtype_arb { + ($name:ident, $ty:ty, $len:expr, $fn_name:ident) => { + fn $fn_name() -> impl Strategy { + collection::vec(arbitrary::any::<$ty>(), $len).prop_map(|values| { + let mut dat = [0u8; $len]; + dat.copy_from_slice(values.as_slice()); + $name(dat) + }) + } + }; +} + +make_array_newtype_arb!(Basic, u8, BASIC_LENGTH, basic_arb); +make_array_newtype_arb!(Similar, u8, SIMILAR_LENGTH, similar_arb); +make_array_newtype_arb!(Fancy, u8, FANCY_LENGTH, fancy_arb); +make_array_newtype_arb!(Long, u8, LONG_LENGTH, long_arb); + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum PublicKey { + Basic(Basic), + Similar(Similar), + Fancy(Fancy), + Long(Long), +} + +impl ToBytes for PublicKey { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut ret = bytesrepr::allocate_buffer(self)?; + match self { + PublicKey::Basic(key) => { + ret.push(PUBLIC_KEY_BASIC_ID); + ret.extend(key.to_bytes()?) + } + PublicKey::Similar(key) => { + ret.push(PUBLIC_KEY_SIMILAR_ID); + ret.extend(key.to_bytes()?) + } + PublicKey::Fancy(key) => { + ret.push(PUBLIC_KEY_FANCY_ID); + ret.extend(key.to_bytes()?) + } + PublicKey::Long(key) => { + ret.push(PUBLIC_KEY_LONG_ID); + ret.extend(key.to_bytes()?) + } + }; + Ok(ret) + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + + match self { + PublicKey::Basic(key) => key.serialized_length(), + PublicKey::Similar(key) => key.serialized_length(), + PublicKey::Fancy(key) => key.serialized_length(), + PublicKey::Long(key) => key.serialized_length(), + } + } +} + +impl FromBytes for PublicKey { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (id, rem): (u8, &[u8]) = FromBytes::from_bytes(bytes)?; + match id { + PUBLIC_KEY_BASIC_ID => { + let (key, rem): (Basic, &[u8]) = FromBytes::from_bytes(rem)?; + Ok((PublicKey::Basic(key), rem)) + } + PUBLIC_KEY_SIMILAR_ID => { + let (key, rem): (Similar, &[u8]) = FromBytes::from_bytes(rem)?; + Ok((PublicKey::Similar(key), rem)) + } + PUBLIC_KEY_FANCY_ID => { + let (key, rem): (Fancy, &[u8]) = FromBytes::from_bytes(rem)?; + Ok((PublicKey::Fancy(key), rem)) + } + PUBLIC_KEY_LONG_ID => { + let (key, rem): (Long, &[u8]) = FromBytes::from_bytes(rem)?; + Ok((PublicKey::Long(key), rem)) + } + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +fn public_key_arb() -> impl Strategy { + prop_oneof![ + basic_arb().prop_map(PublicKey::Basic), + similar_arb().prop_map(PublicKey::Similar), + fancy_arb().prop_map(PublicKey::Fancy), + long_arb().prop_map(PublicKey::Long) + ] +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum TestKey { + Account(PublicKey), + Hash([u8; KEY_HASH_LENGTH]), + URef(URef), +} + +impl ToBytes for TestKey { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut ret = Vec::with_capacity(self.serialized_length()); + match self { + TestKey::Account(public_key) => { + ret.push(KEY_ACCOUNT_ID); + ret.extend(&public_key.to_bytes()?) + } + TestKey::Hash(hash) => { + ret.push(KEY_HASH_ID); + ret.extend(&hash.to_bytes()?) + } + TestKey::URef(uref) => { + ret.push(KEY_UREF_ID); + ret.extend(&uref.to_bytes()?) + } + } + Ok(ret) + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + + match self { + TestKey::Account(public_key) => public_key.serialized_length(), + TestKey::Hash(hash) => hash.serialized_length(), + TestKey::URef(uref) => uref.serialized_length(), + } + } +} + +impl FromBytes for TestKey { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (id, rem): (u8, &[u8]) = FromBytes::from_bytes(bytes)?; + match id { + KEY_ACCOUNT_ID => { + let (public_key, rem): (PublicKey, &[u8]) = FromBytes::from_bytes(rem)?; + Ok((TestKey::Account(public_key), rem)) + } + KEY_HASH_ID => { + let (hash, rem): ([u8; KEY_HASH_LENGTH], &[u8]) = FromBytes::from_bytes(rem)?; + Ok((TestKey::Hash(hash), rem)) + } + KEY_UREF_ID => { + let (uref, rem): (URef, &[u8]) = FromBytes::from_bytes(rem)?; + Ok((TestKey::URef(uref), rem)) + } + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +fn test_key_arb() -> impl Strategy { + prop_oneof![ + public_key_arb().prop_map(TestKey::Account), + gens::u8_slice_32().prop_map(TestKey::Hash), + gens::uref_arb().prop_map(TestKey::URef), + ] +} + +#[allow(clippy::unnecessary_operation)] +mod basics { + use proptest::proptest; + + use super::*; + + #[test] + fn random_key_generation_works_as_expected() { + use rand::Rng; + let mut rng = rand::thread_rng(); + let a: Basic = rng.gen(); + let b: Basic = rng.gen(); + assert_ne!(a, b) + } + + proptest! { + #[test] + fn key_should_roundtrip(key in test_key_arb()) { + bytesrepr::test_serialization_roundtrip(&key) + } + } +} + +type TestTrie = Trie; + +const TEST_LEAVES_LENGTH: usize = 6; + +/// Keys have been chosen deliberately and the `create_` functions below depend +/// on these exact definitions. Values are arbitrary. +const TEST_LEAVES: [TestTrie; TEST_LEAVES_LENGTH] = [ + Trie::Leaf { + key: TestKey::Account(PublicKey::Basic(Basic([0u8, 0, 0, 0]))), + value: TestValue(*b"value0"), + }, + Trie::Leaf { + key: TestKey::Account(PublicKey::Basic(Basic([0u8, 0, 0, 1]))), + value: TestValue(*b"value1"), + }, + Trie::Leaf { + key: TestKey::Account(PublicKey::Similar(Similar([0u8, 0, 0, 1]))), + value: TestValue(*b"value3"), + }, + Trie::Leaf { + key: TestKey::Account(PublicKey::Fancy(Fancy([0u8, 0, 0, 1, 0]))), + value: TestValue(*b"value4"), + }, + Trie::Leaf { + key: TestKey::Account(PublicKey::Long(Long([0u8, 0, 0, 1, 0, 0, 0, 0]))), + value: TestValue(*b"value5"), + }, + Trie::Leaf { + key: TestKey::Hash([0u8; 32]), + value: TestValue(*b"value6"), + }, +]; + +fn create_0_leaf_trie( +) -> Result<(Blake2bHash, Vec>), bytesrepr::Error> { + let root = HashedTrie::new(Trie::node(&[]))?; + + let root_hash: Blake2bHash = root.hash; + + let parents: Vec> = vec![root]; + + let tries: Vec> = { + let mut ret = Vec::new(); + ret.extend(parents); + ret + }; + + Ok((root_hash, tries)) +} + +mod empty_tries { + use types::newtypes::CorrelationId; + + use super::*; + use crate::{ + error::in_memory, + trie_store::operations::tests::{self, InMemoryTestContext}, + }; + + #[test] + fn in_memory_writes_to_n_leaf_empty_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = create_0_leaf_trie().unwrap(); + let context = InMemoryTestContext::new(&tries).unwrap(); + let initial_states = vec![root_hash]; + + let _states = tests::writes_to_n_leaf_empty_trie_had_expected_results::< + _, + _, + _, + _, + in_memory::Error, + >( + correlation_id, + &context.environment, + &context.store, + &initial_states, + &TEST_LEAVES, + ) + .unwrap(); + } +} + +mod proptests { + use proptest::{collection::vec, proptest}; + + use types::newtypes::CorrelationId; + + const DEFAULT_MIN_LENGTH: usize = 0; + const DEFAULT_MAX_LENGTH: usize = 100; + + fn get_range() -> RangeInclusive { + let start = option_env!("CL_TRIE_TEST_VECTOR_MIN_LENGTH") + .and_then(|s| str::parse::(s).ok()) + .unwrap_or(DEFAULT_MIN_LENGTH); + let end = option_env!("CL_TRIE_TEST_VECTOR_MAX_LENGTH") + .and_then(|s| str::parse::(s).ok()) + .unwrap_or(DEFAULT_MAX_LENGTH); + RangeInclusive::new(start, end) + } + + use super::*; + use crate::{ + error::{self, in_memory}, + trie_store::operations::tests::{self, InMemoryTestContext, LmdbTestContext}, + }; + use std::ops::RangeInclusive; + + fn lmdb_roundtrip_succeeds(pairs: &[(TestKey, TestValue)]) -> bool { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = create_0_leaf_trie().unwrap(); + let context = LmdbTestContext::new(&tries).unwrap(); + let mut states_to_check = vec![]; + + let root_hashes = tests::write_pairs::<_, _, _, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &root_hash, + pairs, + ) + .unwrap(); + + states_to_check.extend(root_hashes); + + tests::check_pairs::<_, _, _, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &states_to_check, + &pairs, + ) + .unwrap() + } + + fn in_memory_roundtrip_succeeds(pairs: &[(TestKey, TestValue)]) -> bool { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = create_0_leaf_trie().unwrap(); + let context = InMemoryTestContext::new(&tries).unwrap(); + let mut states_to_check = vec![]; + + let root_hashes = tests::write_pairs::<_, _, _, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &root_hash, + pairs, + ) + .unwrap(); + + states_to_check.extend(root_hashes); + + tests::check_pairs::<_, _, _, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &states_to_check, + &pairs, + ) + .unwrap() + } + + fn test_value_arb() -> impl Strategy { + array::uniform6(arbitrary::any::()).prop_map(TestValue) + } + + proptest! { + #[test] + fn prop_in_memory_roundtrip_succeeds(inputs in vec((test_key_arb(), test_value_arb()), get_range())) { + assert!(in_memory_roundtrip_succeeds(&inputs)); + } + + #[test] + fn prop_lmdb_roundtrip_succeeds(inputs in vec((test_key_arb(), test_value_arb()), get_range())) { + assert!(lmdb_roundtrip_succeeds(&inputs)); + } + } +} diff --git a/node/src/contract_storage/trie_store/operations/tests/keys.rs b/node/src/contract_storage/trie_store/operations/tests/keys.rs new file mode 100644 index 0000000000..f9b0c8ef07 --- /dev/null +++ b/node/src/contract_storage/trie_store/operations/tests/keys.rs @@ -0,0 +1,309 @@ +mod partial_tries { + use crate::contract_shared::newtypes::CorrelationId; + + use crate::contract_storage::{ + transaction_source::{Transaction, TransactionSource}, + trie::Trie, + trie_store::operations::{ + self, + tests::{ + InMemoryTestContext, LmdbTestContext, TestKey, TestValue, TEST_LEAVES, + TEST_TRIE_GENERATORS, + }, + }, + }; + + #[test] + fn lmdb_keys_from_n_leaf_partial_trie_had_expected_results() { + for (num_leaves, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = generator().unwrap(); + let context = LmdbTestContext::new(&tries).unwrap(); + let test_leaves = TEST_LEAVES; + let (used, _) = test_leaves.split_at(num_leaves); + + let expected = { + let mut tmp = used + .iter() + .filter_map(Trie::key) + .cloned() + .collect::>(); + tmp.sort(); + tmp + }; + let actual = { + let txn = context.environment.create_read_txn().unwrap(); + let mut tmp = operations::keys::( + correlation_id, + &txn, + &context.store, + &root_hash, + ) + .filter_map(Result::ok) + .collect::>(); + txn.commit().unwrap(); + tmp.sort(); + tmp + }; + assert_eq!(actual, expected); + } + } + + #[test] + fn in_memory_keys_from_n_leaf_partial_trie_had_expected_results() { + for (num_leaves, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = generator().unwrap(); + let context = InMemoryTestContext::new(&tries).unwrap(); + let test_leaves = TEST_LEAVES; + let (used, _) = test_leaves.split_at(num_leaves); + + let expected = { + let mut tmp = used + .iter() + .filter_map(Trie::key) + .cloned() + .collect::>(); + tmp.sort(); + tmp + }; + let actual = { + let txn = context.environment.create_read_txn().unwrap(); + let mut tmp = operations::keys::( + correlation_id, + &txn, + &context.store, + &root_hash, + ) + .filter_map(Result::ok) + .collect::>(); + txn.commit().unwrap(); + tmp.sort(); + tmp + }; + assert_eq!(actual, expected); + } + } +} + +mod full_tries { + use crate::contract_shared::newtypes::{Blake2bHash, CorrelationId}; + + use crate::contract_storage::{ + transaction_source::{Transaction, TransactionSource}, + trie::Trie, + trie_store::operations::{ + self, + tests::{ + InMemoryTestContext, TestKey, TestValue, EMPTY_HASHED_TEST_TRIES, TEST_LEAVES, + TEST_TRIE_GENERATORS, + }, + }, + }; + + #[test] + fn in_memory_keys_from_n_leaf_full_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let context = InMemoryTestContext::new(EMPTY_HASHED_TEST_TRIES).unwrap(); + let mut states: Vec = Vec::new(); + + for (state_index, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let (root_hash, tries) = generator().unwrap(); + context.update(&tries).unwrap(); + states.push(root_hash); + + for (num_leaves, state) in states[..state_index].iter().enumerate() { + let test_leaves = TEST_LEAVES; + let (used, _unused) = test_leaves.split_at(num_leaves); + + let expected = { + let mut tmp = used + .iter() + .filter_map(Trie::key) + .cloned() + .collect::>(); + tmp.sort(); + tmp + }; + let actual = { + let txn = context.environment.create_read_txn().unwrap(); + let mut tmp = operations::keys::( + correlation_id, + &txn, + &context.store, + &state, + ) + .filter_map(Result::ok) + .collect::>(); + txn.commit().unwrap(); + tmp.sort(); + tmp + }; + assert_eq!(actual, expected); + } + } + } +} + +#[cfg(debug_assertions)] +mod keys_iterator { + use crate::contract_shared::newtypes::{Blake2bHash, CorrelationId}; + use types::bytesrepr; + + use crate::contract_storage::{ + transaction_source::TransactionSource, + trie::{Pointer, Trie}, + trie_store::operations::{ + self, + tests::{ + hash_test_tries, HashedTestTrie, HashedTrie, InMemoryTestContext, TestKey, + TestValue, TEST_LEAVES, + }, + }, + }; + + fn create_invalid_extension_trie( + ) -> Result<(Blake2bHash, Vec), bytesrepr::Error> { + let leaves = hash_test_tries(&TEST_LEAVES[2..3])?; + let ext_1 = HashedTrie::new(Trie::extension( + vec![0u8, 0], + Pointer::NodePointer(leaves[0].hash), + ))?; + + let root = HashedTrie::new(Trie::node(&[(0, Pointer::NodePointer(ext_1.hash))]))?; + let root_hash = root.hash; + + let tries = vec![root, ext_1, leaves[0].clone()]; + + Ok((root_hash, tries)) + } + + fn create_invalid_path_trie() -> Result<(Blake2bHash, Vec), bytesrepr::Error> { + let leaves = hash_test_tries(&TEST_LEAVES[..1])?; + + let root = HashedTrie::new(Trie::node(&[(1, Pointer::NodePointer(leaves[0].hash))]))?; + let root_hash = root.hash; + + let tries = vec![root, leaves[0].clone()]; + + Ok((root_hash, tries)) + } + + fn create_invalid_hash_trie() -> Result<(Blake2bHash, Vec), bytesrepr::Error> { + let leaves = hash_test_tries(&TEST_LEAVES[..2])?; + + let root = HashedTrie::new(Trie::node(&[(0, Pointer::NodePointer(leaves[1].hash))]))?; + let root_hash = root.hash; + + let tries = vec![root, leaves[0].clone()]; + + Ok((root_hash, tries)) + } + + macro_rules! return_on_err { + ($x:expr) => { + match $x { + Ok(result) => result, + Err(_) => { + return; // we expect the test to panic, so this will cause a test failure + } + } + }; + } + + fn test_trie(root_hash: Blake2bHash, tries: Vec) { + let correlation_id = CorrelationId::new(); + let context = return_on_err!(InMemoryTestContext::new(&tries)); + let txn = return_on_err!(context.environment.create_read_txn()); + let _tmp = operations::keys::( + correlation_id, + &txn, + &context.store, + &root_hash, + ) + .collect::>(); + } + + #[test] + #[should_panic] + fn should_panic_on_leaf_after_extension() { + let (root_hash, tries) = return_on_err!(create_invalid_extension_trie()); + test_trie(root_hash, tries); + } + + #[test] + #[should_panic] + fn should_panic_when_key_not_matching_path() { + let (root_hash, tries) = return_on_err!(create_invalid_path_trie()); + test_trie(root_hash, tries); + } + + #[test] + #[should_panic] + fn should_panic_on_pointer_to_nonexisting_hash() { + let (root_hash, tries) = return_on_err!(create_invalid_hash_trie()); + test_trie(root_hash, tries); + } +} + +mod keys_with_prefix_iterator { + use crate::contract_shared::newtypes::CorrelationId; + + use crate::contract_storage::{ + transaction_source::TransactionSource, + trie::Trie, + trie_store::operations::{ + self, + tests::{create_6_leaf_trie, InMemoryTestContext, TestKey, TestValue, TEST_LEAVES}, + }, + }; + + fn expected_keys(prefix: &[u8]) -> Vec { + let mut tmp = TEST_LEAVES + .iter() + .filter_map(Trie::key) + .filter(|key| key.0.starts_with(prefix)) + .cloned() + .collect::>(); + tmp.sort(); + tmp + } + + fn test_prefix(prefix: &[u8]) { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = create_6_leaf_trie().expect("should create a trie"); + let context = InMemoryTestContext::new(&tries).expect("should create a new context"); + let txn = context + .environment + .create_read_txn() + .expect("should create a read txn"); + let expected = expected_keys(prefix); + let mut actual = operations::keys_with_prefix::( + correlation_id, + &txn, + &context.store, + &root_hash, + prefix, + ) + .filter_map(Result::ok) + .collect::>(); + actual.sort(); + assert_eq!(expected, actual); + } + + #[test] + fn test_prefixes() { + test_prefix(&[]); // 6 leaves + test_prefix(&[0]); // 6 leaves + test_prefix(&[0, 1]); // 1 leaf + test_prefix(&[0, 1, 0]); // 1 leaf + test_prefix(&[0, 1, 1]); // 0 leaves + test_prefix(&[0, 0]); // 5 leaves + test_prefix(&[0, 0, 1]); // 0 leaves + test_prefix(&[0, 0, 2]); // 1 leaf + test_prefix(&[0, 0, 0, 0]); // 3 leaves, prefix points to an Extension + test_prefix(&[0, 0, 0, 0, 0]); // 3 leaves + test_prefix(&[0, 0, 0, 0, 0, 0]); // 2 leaves + test_prefix(&[0, 0, 0, 0, 0, 0, 1]); // 1 leaf + } +} diff --git a/node/src/contract_storage/trie_store/operations/tests/mod.rs b/node/src/contract_storage/trie_store/operations/tests/mod.rs new file mode 100644 index 0000000000..211d6f9b47 --- /dev/null +++ b/node/src/contract_storage/trie_store/operations/tests/mod.rs @@ -0,0 +1,862 @@ +mod keys; +mod proptests; +mod read; +mod scan; +mod write; + +use std::{collections::HashMap, convert}; + +use lmdb::DatabaseFlags; +use tempfile::{tempdir, TempDir}; + +use crate::contract_shared::newtypes::{Blake2bHash, CorrelationId}; +use types::bytesrepr::{self, FromBytes, ToBytes}; + +use crate::contract_storage::{ + error::{self, in_memory}, + transaction_source::{ + in_memory::InMemoryEnvironment, lmdb::LmdbEnvironment, Readable, Transaction, + TransactionSource, + }, + trie::{Pointer, Trie}, + trie_store::{ + self, + in_memory::InMemoryTrieStore, + lmdb::LmdbTrieStore, + operations::{self, read, write, ReadResult, WriteResult}, + TrieStore, + }, + TEST_MAP_SIZE, +}; + +const TEST_KEY_LENGTH: usize = 7; + +/// A short key type for tests. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +struct TestKey([u8; TEST_KEY_LENGTH]); + +impl ToBytes for TestKey { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + Ok(self.0.to_vec()) + } + + fn serialized_length(&self) -> usize { + TEST_KEY_LENGTH + } +} + +impl FromBytes for TestKey { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (key, rem) = bytes.split_at(TEST_KEY_LENGTH); + let mut ret = [0u8; TEST_KEY_LENGTH]; + ret.copy_from_slice(key); + Ok((TestKey(ret), rem)) + } +} + +const TEST_VAL_LENGTH: usize = 6; + +/// A short value type for tests. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +struct TestValue([u8; TEST_VAL_LENGTH]); + +impl ToBytes for TestValue { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + Ok(self.0.to_vec()) + } + + fn serialized_length(&self) -> usize { + TEST_VAL_LENGTH + } +} + +impl FromBytes for TestValue { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (key, rem) = bytes.split_at(TEST_VAL_LENGTH); + let mut ret = [0u8; TEST_VAL_LENGTH]; + ret.copy_from_slice(key); + Ok((TestValue(ret), rem)) + } +} + +type TestTrie = Trie; + +type HashedTestTrie = HashedTrie; + +/// A pairing of a trie element and its hash. +#[derive(Debug, Clone, PartialEq, Eq)] +struct HashedTrie { + hash: Blake2bHash, + trie: Trie, +} + +impl HashedTrie { + pub fn new(trie: Trie) -> Result { + let trie_bytes = trie.to_bytes()?; + let hash = Blake2bHash::new(&trie_bytes); + Ok(HashedTrie { hash, trie }) + } +} + +const EMPTY_HASHED_TEST_TRIES: &[HashedTestTrie] = &[]; + +const TEST_LEAVES_LENGTH: usize = 6; + +/// Keys have been chosen deliberately and the `create_` functions below depend +/// on these exact definitions. Values are arbitrary. +const TEST_LEAVES: [TestTrie; TEST_LEAVES_LENGTH] = [ + Trie::Leaf { + key: TestKey([0u8, 0, 0, 0, 0, 0, 0]), + value: TestValue(*b"value0"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 0, 0, 0, 0, 1]), + value: TestValue(*b"value1"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 0, 2, 0, 0, 0]), + value: TestValue(*b"value2"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 0, 0, 0, 255, 0]), + value: TestValue(*b"value3"), + }, + Trie::Leaf { + key: TestKey([0u8, 1, 0, 0, 0, 0, 0]), + value: TestValue(*b"value4"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 2, 0, 0, 0, 0]), + value: TestValue(*b"value5"), + }, +]; + +const TEST_LEAVES_UPDATED: [TestTrie; TEST_LEAVES_LENGTH] = [ + Trie::Leaf { + key: TestKey([0u8, 0, 0, 0, 0, 0, 0]), + value: TestValue(*b"valueA"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 0, 0, 0, 0, 1]), + value: TestValue(*b"valueB"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 0, 2, 0, 0, 0]), + value: TestValue(*b"valueC"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 0, 0, 0, 255, 0]), + value: TestValue(*b"valueD"), + }, + Trie::Leaf { + key: TestKey([0u8, 1, 0, 0, 0, 0, 0]), + value: TestValue(*b"valueE"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 2, 0, 0, 0, 0]), + value: TestValue(*b"valueF"), + }, +]; + +const TEST_LEAVES_NON_COLLIDING: [TestTrie; TEST_LEAVES_LENGTH] = [ + Trie::Leaf { + key: TestKey([0u8, 0, 0, 0, 0, 0, 0]), + value: TestValue(*b"valueA"), + }, + Trie::Leaf { + key: TestKey([1u8, 0, 0, 0, 0, 0, 0]), + value: TestValue(*b"valueB"), + }, + Trie::Leaf { + key: TestKey([2u8, 0, 0, 0, 0, 0, 0]), + value: TestValue(*b"valueC"), + }, + Trie::Leaf { + key: TestKey([3u8, 0, 0, 0, 0, 0, 0]), + value: TestValue(*b"valueD"), + }, + Trie::Leaf { + key: TestKey([4u8, 0, 0, 0, 0, 0, 0]), + value: TestValue(*b"valueE"), + }, + Trie::Leaf { + key: TestKey([5u8, 0, 0, 0, 0, 0, 0]), + value: TestValue(*b"valueF"), + }, +]; + +const TEST_LEAVES_ADJACENTS: [TestTrie; TEST_LEAVES_LENGTH] = [ + Trie::Leaf { + key: TestKey([0u8, 0, 0, 0, 0, 0, 2]), + value: TestValue(*b"valueA"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 0, 0, 0, 0, 3]), + value: TestValue(*b"valueB"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 0, 3, 0, 0, 0]), + value: TestValue(*b"valueC"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 0, 0, 0, 1, 0]), + value: TestValue(*b"valueD"), + }, + Trie::Leaf { + key: TestKey([0u8, 2, 0, 0, 0, 0, 0]), + value: TestValue(*b"valueE"), + }, + Trie::Leaf { + key: TestKey([0u8, 0, 3, 0, 0, 0, 0]), + value: TestValue(*b"valueF"), + }, +]; + +type TrieGenerator = fn() -> Result<(Blake2bHash, Vec>), bytesrepr::Error>; + +const TEST_TRIE_GENERATORS_LENGTH: usize = 7; + +const TEST_TRIE_GENERATORS: [TrieGenerator; TEST_TRIE_GENERATORS_LENGTH] = [ + create_0_leaf_trie, + create_1_leaf_trie, + create_2_leaf_trie, + create_3_leaf_trie, + create_4_leaf_trie, + create_5_leaf_trie, + create_6_leaf_trie, +]; + +fn hash_test_tries(tries: &[TestTrie]) -> Result, bytesrepr::Error> { + tries + .iter() + .map(|trie| HashedTestTrie::new(trie.to_owned())) + .collect() +} + +fn create_0_leaf_trie() -> Result<(Blake2bHash, Vec), bytesrepr::Error> { + let root = HashedTrie::new(Trie::node(&[]))?; + + let root_hash: Blake2bHash = root.hash; + + let parents: Vec = vec![root]; + + let tries: Vec = { + let mut ret = Vec::new(); + ret.extend(parents); + ret + }; + + Ok((root_hash, tries)) +} + +fn create_1_leaf_trie() -> Result<(Blake2bHash, Vec), bytesrepr::Error> { + let leaves = hash_test_tries(&TEST_LEAVES[..1])?; + + let root = HashedTrie::new(Trie::node(&[(0, Pointer::LeafPointer(leaves[0].hash))]))?; + + let root_hash: Blake2bHash = root.hash; + + let parents: Vec = vec![root]; + + let tries: Vec = { + let mut ret = Vec::new(); + ret.extend(leaves); + ret.extend(parents); + ret + }; + + Ok((root_hash, tries)) +} + +fn create_2_leaf_trie() -> Result<(Blake2bHash, Vec), bytesrepr::Error> { + let leaves = hash_test_tries(&TEST_LEAVES[..2])?; + + let node = HashedTrie::new(Trie::node(&[ + (0, Pointer::LeafPointer(leaves[0].hash)), + (1, Pointer::LeafPointer(leaves[1].hash)), + ]))?; + + let ext = HashedTrie::new(Trie::extension( + vec![0u8, 0, 0, 0, 0], + Pointer::NodePointer(node.hash), + ))?; + + let root = HashedTrie::new(Trie::node(&[(0, Pointer::NodePointer(ext.hash))]))?; + + let root_hash = root.hash; + + let parents: Vec = vec![root, ext, node]; + + let tries: Vec = { + let mut ret = Vec::new(); + ret.extend(leaves); + ret.extend(parents); + ret + }; + + Ok((root_hash, tries)) +} + +fn create_3_leaf_trie() -> Result<(Blake2bHash, Vec), bytesrepr::Error> { + let leaves = hash_test_tries(&TEST_LEAVES[..3])?; + + let node_1 = HashedTrie::new(Trie::node(&[ + (0, Pointer::LeafPointer(leaves[0].hash)), + (1, Pointer::LeafPointer(leaves[1].hash)), + ]))?; + + let ext_1 = HashedTrie::new(Trie::extension( + vec![0u8, 0], + Pointer::NodePointer(node_1.hash), + ))?; + + let node_2 = HashedTrie::new(Trie::node(&[ + (0, Pointer::NodePointer(ext_1.hash)), + (2, Pointer::LeafPointer(leaves[2].hash)), + ]))?; + + let ext_2 = HashedTrie::new(Trie::extension( + vec![0u8, 0], + Pointer::NodePointer(node_2.hash), + ))?; + + let root = HashedTrie::new(Trie::node(&[(0, Pointer::NodePointer(ext_2.hash))]))?; + + let root_hash = root.hash; + + let parents: Vec = vec![root, ext_2, node_2, ext_1, node_1]; + + let tries: Vec = { + let mut ret = Vec::new(); + ret.extend(leaves); + ret.extend(parents); + ret + }; + + Ok((root_hash, tries)) +} + +fn create_4_leaf_trie() -> Result<(Blake2bHash, Vec), bytesrepr::Error> { + let leaves = hash_test_tries(&TEST_LEAVES[..4])?; + + let node_1 = HashedTrie::new(Trie::node(&[ + (0, Pointer::LeafPointer(leaves[0].hash)), + (1, Pointer::LeafPointer(leaves[1].hash)), + ]))?; + + let node_2 = HashedTrie::new(Trie::node(&[ + (0, Pointer::NodePointer(node_1.hash)), + (255, Pointer::LeafPointer(leaves[3].hash)), + ]))?; + + let ext_1 = HashedTrie::new(Trie::extension( + vec![0u8], + Pointer::NodePointer(node_2.hash), + ))?; + + let node_3 = HashedTrie::new(Trie::node(&[ + (0, Pointer::NodePointer(ext_1.hash)), + (2, Pointer::LeafPointer(leaves[2].hash)), + ]))?; + + let ext_2 = HashedTrie::new(Trie::extension( + vec![0u8, 0], + Pointer::NodePointer(node_3.hash), + ))?; + + let root = HashedTrie::new(Trie::node(&[(0, Pointer::NodePointer(ext_2.hash))]))?; + + let root_hash = root.hash; + + let parents: Vec = vec![root, ext_2, node_3, ext_1, node_2, node_1]; + + let tries: Vec = { + let mut ret = Vec::new(); + ret.extend(leaves); + ret.extend(parents); + ret + }; + + Ok((root_hash, tries)) +} + +fn create_5_leaf_trie() -> Result<(Blake2bHash, Vec), bytesrepr::Error> { + let leaves = hash_test_tries(&TEST_LEAVES[..5])?; + + let node_1 = HashedTrie::new(Trie::node(&[ + (0, Pointer::LeafPointer(leaves[0].hash)), + (1, Pointer::LeafPointer(leaves[1].hash)), + ]))?; + + let node_2 = HashedTrie::new(Trie::node(&[ + (0, Pointer::NodePointer(node_1.hash)), + (255, Pointer::LeafPointer(leaves[3].hash)), + ]))?; + + let ext_1 = HashedTrie::new(Trie::extension( + vec![0u8], + Pointer::NodePointer(node_2.hash), + ))?; + + let node_3 = HashedTrie::new(Trie::node(&[ + (0, Pointer::NodePointer(ext_1.hash)), + (2, Pointer::LeafPointer(leaves[2].hash)), + ]))?; + + let ext_2 = HashedTrie::new(Trie::extension( + vec![0u8], + Pointer::NodePointer(node_3.hash), + ))?; + + let node_4 = HashedTrie::new(Trie::node(&[ + (0, Pointer::NodePointer(ext_2.hash)), + (1, Pointer::LeafPointer(leaves[4].hash)), + ]))?; + + let root = HashedTrie::new(Trie::node(&[(0, Pointer::NodePointer(node_4.hash))]))?; + + let root_hash = root.hash; + + let parents: Vec = vec![root, node_4, ext_2, node_3, ext_1, node_2, node_1]; + + let tries: Vec = { + let mut ret = Vec::new(); + ret.extend(leaves); + ret.extend(parents); + ret + }; + + Ok((root_hash, tries)) +} + +fn create_6_leaf_trie() -> Result<(Blake2bHash, Vec), bytesrepr::Error> { + let leaves = hash_test_tries(&TEST_LEAVES)?; + + let node_1 = HashedTrie::new(Trie::node(&[ + (0, Pointer::LeafPointer(leaves[0].hash)), + (1, Pointer::LeafPointer(leaves[1].hash)), + ]))?; + + let node_2 = HashedTrie::new(Trie::node(&[ + (0, Pointer::NodePointer(node_1.hash)), + (255, Pointer::LeafPointer(leaves[3].hash)), + ]))?; + + let ext = HashedTrie::new(Trie::extension( + vec![0u8], + Pointer::NodePointer(node_2.hash), + ))?; + + let node_3 = HashedTrie::new(Trie::node(&[ + (0, Pointer::NodePointer(ext.hash)), + (2, Pointer::LeafPointer(leaves[2].hash)), + ]))?; + + let node_4 = HashedTrie::new(Trie::node(&[ + (0, Pointer::NodePointer(node_3.hash)), + (2, Pointer::LeafPointer(leaves[5].hash)), + ]))?; + + let node_5 = HashedTrie::new(Trie::node(&[ + (0, Pointer::NodePointer(node_4.hash)), + (1, Pointer::LeafPointer(leaves[4].hash)), + ]))?; + + let root = HashedTrie::new(Trie::node(&[(0, Pointer::NodePointer(node_5.hash))]))?; + + let root_hash = root.hash; + + let parents: Vec = vec![root, node_5, node_4, node_3, ext, node_2, node_1]; + + let tries: Vec = { + let mut ret = Vec::new(); + ret.extend(leaves); + ret.extend(parents); + ret + }; + + Ok((root_hash, tries)) +} + +fn put_tries<'a, K, V, R, S, E>( + environment: &'a R, + store: &S, + tries: &[HashedTrie], +) -> Result<(), E> +where + K: ToBytes, + V: ToBytes, + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, +{ + if tries.is_empty() { + return Ok(()); + } + let mut txn = environment.create_read_write_txn()?; + for HashedTrie { hash, trie } in tries.iter() { + store.put(&mut txn, hash, trie)?; + } + txn.commit()?; + Ok(()) +} + +// A context for holding lmdb-based test resources +struct LmdbTestContext { + _temp_dir: TempDir, + environment: LmdbEnvironment, + store: LmdbTrieStore, +} + +impl LmdbTestContext { + fn new(tries: &[HashedTrie]) -> Result + where + K: FromBytes + ToBytes, + V: FromBytes + ToBytes, + { + let _temp_dir = tempdir()?; + let environment = LmdbEnvironment::new(&_temp_dir.path().to_path_buf(), *TEST_MAP_SIZE)?; + let store = LmdbTrieStore::new(&environment, None, DatabaseFlags::empty())?; + put_tries::<_, _, _, _, error::Error>(&environment, &store, tries)?; + Ok(LmdbTestContext { + _temp_dir, + environment, + store, + }) + } + + fn update(&self, tries: &[HashedTrie]) -> Result<(), failure::Error> + where + K: ToBytes, + V: ToBytes, + { + put_tries::<_, _, _, _, error::Error>(&self.environment, &self.store, tries)?; + Ok(()) + } +} + +// A context for holding in-memory test resources +struct InMemoryTestContext { + environment: InMemoryEnvironment, + store: InMemoryTrieStore, +} + +impl InMemoryTestContext { + fn new(tries: &[HashedTrie]) -> Result + where + K: ToBytes, + V: ToBytes, + { + let environment = InMemoryEnvironment::new(); + let store = InMemoryTrieStore::new(&environment, None); + put_tries::<_, _, _, _, in_memory::Error>(&environment, &store, tries)?; + Ok(InMemoryTestContext { environment, store }) + } + + fn update(&self, tries: &[HashedTrie]) -> Result<(), failure::Error> + where + K: ToBytes, + V: ToBytes, + { + put_tries::<_, _, _, _, in_memory::Error>(&self.environment, &self.store, tries)?; + Ok(()) + } +} + +fn check_leaves_exist( + correlation_id: CorrelationId, + txn: &T, + store: &S, + root: &Blake2bHash, + leaves: &[Trie], +) -> Result, E> +where + K: ToBytes + FromBytes + Eq + std::fmt::Debug, + V: ToBytes + FromBytes + Eq + Copy, + T: Readable, + S: TrieStore, + S::Error: From, + E: From + From, +{ + let mut ret = Vec::new(); + + for leaf in leaves { + if let Trie::Leaf { key, value } = leaf { + let maybe_value: ReadResult = + read::<_, _, _, _, E>(correlation_id, txn, store, root, key)?; + ret.push(ReadResult::Found(*value) == maybe_value) + } else { + panic!("leaves should only contain leaves") + } + } + Ok(ret) +} + +fn check_keys( + correlation_id: CorrelationId, + txn: &T, + store: &S, + root: &Blake2bHash, + leaves: &[Trie], +) -> Result +where + K: ToBytes + FromBytes + Eq + std::fmt::Debug + Clone + Ord, + V: ToBytes + FromBytes + Eq + std::fmt::Debug + Copy, + T: Readable, + S: TrieStore, + S::Error: From, + E: From + From, +{ + let expected = { + let mut tmp = leaves + .iter() + .filter_map(Trie::key) + .cloned() + .collect::>(); + tmp.sort(); + tmp + }; + let actual = { + let mut tmp = operations::keys::<_, _, _, _>(correlation_id, txn, store, root) + .filter_map(Result::ok) + .collect::>(); + tmp.sort(); + tmp + }; + Ok(expected == actual) +} + +fn check_leaves<'a, K, V, R, S, E>( + correlation_id: CorrelationId, + environment: &'a R, + store: &S, + root: &Blake2bHash, + present: &[Trie], + absent: &[Trie], +) -> Result<(), E> +where + K: ToBytes + FromBytes + Eq + std::fmt::Debug + Clone + Ord, + V: ToBytes + FromBytes + Eq + std::fmt::Debug + Copy, + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, +{ + let txn: R::ReadTransaction = environment.create_read_txn()?; + + assert!( + check_leaves_exist::<_, _, _, _, E>(correlation_id, &txn, store, root, present)? + .into_iter() + .all(convert::identity) + ); + + assert!( + check_leaves_exist::<_, _, _, _, E>(correlation_id, &txn, store, root, absent)? + .into_iter() + .all(|b| !b) + ); + + assert!(check_keys::<_, _, _, _, E>( + correlation_id, + &txn, + store, + root, + present, + )?); + + txn.commit()?; + Ok(()) +} + +fn write_leaves<'a, K, V, R, S, E>( + correlation_id: CorrelationId, + environment: &'a R, + store: &S, + root_hash: &Blake2bHash, + leaves: &[Trie], +) -> Result, E> +where + K: ToBytes + FromBytes + Clone + Eq + std::fmt::Debug, + V: ToBytes + FromBytes + Clone + Eq, + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, +{ + let mut results = Vec::new(); + if leaves.is_empty() { + return Ok(results); + } + let mut root_hash = root_hash.to_owned(); + let mut txn = environment.create_read_write_txn()?; + + for leaf in leaves.iter() { + if let Trie::Leaf { key, value } = leaf { + let write_result = + write::<_, _, _, _, E>(correlation_id, &mut txn, store, &root_hash, key, value)?; + match write_result { + WriteResult::Written(hash) => { + root_hash = hash; + } + WriteResult::AlreadyExists => (), + WriteResult::RootNotFound => panic!("write_leaves given an invalid root"), + }; + results.push(write_result); + } else { + panic!("leaves should contain only leaves"); + } + } + txn.commit()?; + Ok(results) +} + +fn check_pairs<'a, K, V, R, S, E>( + correlation_id: CorrelationId, + environment: &'a R, + store: &S, + root_hashes: &[Blake2bHash], + pairs: &[(K, V)], +) -> Result +where + K: ToBytes + FromBytes + Eq + std::fmt::Debug + Clone + Ord, + V: ToBytes + FromBytes + Eq + std::fmt::Debug + Copy, + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, +{ + let txn = environment.create_read_txn()?; + for (index, root_hash) in root_hashes.iter().enumerate() { + for (key, value) in &pairs[..=index] { + let result = read::<_, _, _, _, E>(correlation_id, &txn, store, root_hash, key)?; + if ReadResult::Found(*value) != result { + return Ok(false); + } + } + let expected = { + let mut tmp = pairs[..=index] + .iter() + .map(|(k, _)| k) + .cloned() + .collect::>(); + tmp.sort(); + tmp + }; + let actual = { + let mut tmp = operations::keys::<_, _, _, _>(correlation_id, &txn, store, root_hash) + .filter_map(Result::ok) + .collect::>(); + tmp.sort(); + tmp + }; + if expected != actual { + return Ok(false); + } + } + Ok(true) +} + +fn write_pairs<'a, K, V, R, S, E>( + correlation_id: CorrelationId, + environment: &'a R, + store: &S, + root_hash: &Blake2bHash, + pairs: &[(K, V)], +) -> Result, E> +where + K: ToBytes + FromBytes + Clone + Eq + std::fmt::Debug, + V: ToBytes + FromBytes + Clone + Eq, + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, +{ + let mut results = Vec::new(); + if pairs.is_empty() { + return Ok(results); + } + let mut root_hash = root_hash.to_owned(); + let mut txn = environment.create_read_write_txn()?; + + for (key, value) in pairs.iter() { + match write::<_, _, _, _, E>(correlation_id, &mut txn, store, &root_hash, key, value)? { + WriteResult::Written(hash) => { + root_hash = hash; + } + WriteResult::AlreadyExists => (), + WriteResult::RootNotFound => panic!("write_leaves given an invalid root"), + }; + results.push(root_hash); + } + txn.commit()?; + Ok(results) +} + +fn writes_to_n_leaf_empty_trie_had_expected_results<'a, K, V, R, S, E>( + correlation_id: CorrelationId, + environment: &'a R, + store: &S, + states: &[Blake2bHash], + test_leaves: &[Trie], +) -> Result, E> +where + K: ToBytes + FromBytes + Clone + Eq + std::fmt::Debug + Ord, + V: ToBytes + FromBytes + Clone + Eq + std::fmt::Debug + Copy, + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, +{ + let mut states = states.to_vec(); + + // Write set of leaves to the trie + let hashes = write_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + states.last().unwrap(), + &test_leaves, + )? + .into_iter() + .map(|result| match result { + WriteResult::Written(root_hash) => root_hash, + _ => panic!("write_leaves resulted in non-write"), + }) + .collect::>(); + + states.extend(hashes); + + // Check that the expected set of leaves is in the trie at every + // state, and that the set of other leaves is not. + for (num_leaves, state) in states.iter().enumerate() { + let (used, unused) = test_leaves.split_at(num_leaves); + check_leaves::<_, _, _, _, E>(correlation_id, environment, store, state, used, unused)?; + } + + Ok(states) +} + +impl InMemoryEnvironment { + pub fn dump( + &self, + maybe_name: Option<&str>, + ) -> Result>, in_memory::Error> + where + K: FromBytes, + V: FromBytes, + { + let name = maybe_name + .map(|name| format!("{}-{}", trie_store::NAME, name)) + .unwrap_or_else(|| trie_store::NAME.to_string()); + let data = self.data(Some(&name))?.unwrap(); + data.into_iter() + .map(|(hash_bytes, trie_bytes)| { + let hash: Blake2bHash = bytesrepr::deserialize(hash_bytes.to_vec())?; + let trie: Trie = bytesrepr::deserialize(trie_bytes.to_vec())?; + Ok((hash, trie)) + }) + .collect::>, bytesrepr::Error>>() + .map_err(Into::into) + } +} diff --git a/node/src/contract_storage/trie_store/operations/tests/proptests.rs b/node/src/contract_storage/trie_store/operations/tests/proptests.rs new file mode 100644 index 0000000000..30f164cd0c --- /dev/null +++ b/node/src/contract_storage/trie_store/operations/tests/proptests.rs @@ -0,0 +1,97 @@ +use std::ops::RangeInclusive; + +use proptest::{ + array, + collection::vec, + prelude::{any, proptest, Strategy}, +}; + +use super::*; + +const DEFAULT_MIN_LENGTH: usize = 0; + +const DEFAULT_MAX_LENGTH: usize = 100; + +fn get_range() -> RangeInclusive { + let start = option_env!("CL_TRIE_TEST_VECTOR_MIN_LENGTH") + .and_then(|s| str::parse::(s).ok()) + .unwrap_or(DEFAULT_MIN_LENGTH); + let end = option_env!("CL_TRIE_TEST_VECTOR_MAX_LENGTH") + .and_then(|s| str::parse::(s).ok()) + .unwrap_or(DEFAULT_MAX_LENGTH); + RangeInclusive::new(start, end) +} + +fn lmdb_roundtrip_succeeds(pairs: &[(TestKey, TestValue)]) -> bool { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = TEST_TRIE_GENERATORS[0]().unwrap(); + let context = LmdbTestContext::new(&tries).unwrap(); + let mut states_to_check = vec![]; + + let root_hashes = write_pairs::<_, _, _, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &root_hash, + pairs, + ) + .unwrap(); + + states_to_check.extend(root_hashes); + + check_pairs::<_, _, _, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &states_to_check, + &pairs, + ) + .unwrap() +} + +fn in_memory_roundtrip_succeeds(pairs: &[(TestKey, TestValue)]) -> bool { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = TEST_TRIE_GENERATORS[0]().unwrap(); + let context = InMemoryTestContext::new(&tries).unwrap(); + let mut states_to_check = vec![]; + + let root_hashes = write_pairs::<_, _, _, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &root_hash, + pairs, + ) + .unwrap(); + + states_to_check.extend(root_hashes); + + check_pairs::<_, _, _, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &states_to_check, + &pairs, + ) + .unwrap() +} + +fn test_key_arb() -> impl Strategy { + array::uniform7(any::()).prop_map(TestKey) +} + +fn test_value_arb() -> impl Strategy { + array::uniform6(any::()).prop_map(TestValue) +} + +proptest! { + #[test] + fn prop_in_memory_roundtrip_succeeds(inputs in vec((test_key_arb(), test_value_arb()), get_range())) { + assert!(in_memory_roundtrip_succeeds(&inputs)); + } + + #[test] + fn prop_lmdb_roundtrip_succeeds(inputs in vec((test_key_arb(), test_value_arb()), get_range())) { + assert!(lmdb_roundtrip_succeeds(&inputs)); + } +} diff --git a/node/src/contract_storage/trie_store/operations/tests/read.rs b/node/src/contract_storage/trie_store/operations/tests/read.rs new file mode 100644 index 0000000000..44e84751d8 --- /dev/null +++ b/node/src/contract_storage/trie_store/operations/tests/read.rs @@ -0,0 +1,128 @@ +//! This module contains tests for [`StateReader::read`]. +//! +//! Our primary goal here is to test this functionality in isolation. +//! Therefore, we manually construct test tries from a well-known set of +//! leaves called [`TEST_LEAVES`](super::TEST_LEAVES), each of which represents a value we are +//! trying to store in the trie at a given key. +//! +//! We use two strategies for testing. See the [`partial_tries`] and +//! [`full_tries`] modules for more info. + +use super::*; +use crate::contract_storage::error::{self, in_memory}; + +mod partial_tries { + //! Here we construct 6 separate "partial" tries, increasing in size + //! from 0 to 5 leaves. Each of these tries contains no past history, + //! only a single a root to read from. The tests check that we can read + //! only the expected set of leaves from the trie from this single root. + + use super::*; + + #[test] + fn lmdb_reads_from_n_leaf_partial_trie_had_expected_results() { + for (num_leaves, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = generator().unwrap(); + let context = LmdbTestContext::new(&tries).unwrap(); + let test_leaves = TEST_LEAVES; + let (used, unused) = test_leaves.split_at(num_leaves); + + check_leaves::<_, _, _, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &root_hash, + used, + unused, + ) + .unwrap(); + } + } + + #[test] + fn in_memory_reads_from_n_leaf_partial_trie_had_expected_results() { + for (num_leaves, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = generator().unwrap(); + let context = InMemoryTestContext::new(&tries).unwrap(); + let test_leaves = TEST_LEAVES; + let (used, unused) = test_leaves.split_at(num_leaves); + + check_leaves::<_, _, _, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &root_hash, + used, + unused, + ) + .unwrap(); + } + } +} + +mod full_tries { + //! Here we construct a series of 6 "full" tries, increasing in size + //! from 0 to 5 leaves. Each trie contains the history from preceding + //! tries in this series, and past history can be read from the roots of + //! each preceding trie. The tests check that we can read only the + //! expected set of leaves from the trie at the current root and all past + //! roots. + + use super::*; + + #[test] + fn lmdb_reads_from_n_leaf_full_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let context = LmdbTestContext::new(EMPTY_HASHED_TEST_TRIES).unwrap(); + let mut states: Vec = Vec::new(); + + for (state_index, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let (root_hash, tries) = generator().unwrap(); + context.update(&tries).unwrap(); + states.push(root_hash); + + for (num_leaves, state) in states[..state_index].iter().enumerate() { + let test_leaves = TEST_LEAVES; + let (used, unused) = test_leaves.split_at(num_leaves); + check_leaves::<_, _, _, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + state, + used, + unused, + ) + .unwrap(); + } + } + } + + #[test] + fn in_memory_reads_from_n_leaf_full_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let context = InMemoryTestContext::new(EMPTY_HASHED_TEST_TRIES).unwrap(); + let mut states: Vec = Vec::new(); + + for (state_index, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let (root_hash, tries) = generator().unwrap(); + context.update(&tries).unwrap(); + states.push(root_hash); + + for (num_leaves, state) in states[..state_index].iter().enumerate() { + let test_leaves = TEST_LEAVES; + let (used, unused) = test_leaves.split_at(num_leaves); + check_leaves::<_, _, _, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + state, + used, + unused, + ) + .unwrap(); + } + } + } +} diff --git a/node/src/contract_storage/trie_store/operations/tests/scan.rs b/node/src/contract_storage/trie_store/operations/tests/scan.rs new file mode 100644 index 0000000000..4387ce50cf --- /dev/null +++ b/node/src/contract_storage/trie_store/operations/tests/scan.rs @@ -0,0 +1,160 @@ +use crate::contract_shared::newtypes::Blake2bHash; + +use super::*; +use crate::contract_storage::{ + error::{self, in_memory}, + trie_store::operations::{scan, TrieScan}, +}; + +fn check_scan<'a, R, S, E>( + correlation_id: CorrelationId, + environment: &'a R, + store: &S, + root_hash: &Blake2bHash, + key: &[u8], +) -> Result<(), E> +where + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From + std::fmt::Debug, + E: From + From + From, +{ + let txn: R::ReadTransaction = environment.create_read_txn()?; + let root = store + .get(&txn, &root_hash)? + .expect("check_scan received an invalid root hash"); + let TrieScan { mut tip, parents } = scan::( + correlation_id, + &txn, + store, + key, + &root, + )?; + + for (index, parent) in parents.into_iter().rev() { + let expected_tip_hash = { + let tip_bytes = tip.to_bytes().unwrap(); + Blake2bHash::new(&tip_bytes) + }; + match parent { + Trie::Leaf { .. } => panic!("parents should not contain any leaves"), + Trie::Node { pointer_block } => { + let pointer_tip_hash = pointer_block[::from(index)].map(|ptr| *ptr.hash()); + assert_eq!(Some(expected_tip_hash), pointer_tip_hash); + tip = Trie::Node { pointer_block }; + } + Trie::Extension { affix, pointer } => { + let pointer_tip_hash = pointer.hash().to_owned(); + assert_eq!(expected_tip_hash, pointer_tip_hash); + tip = Trie::Extension { affix, pointer }; + } + } + } + assert_eq!(root, tip); + txn.commit()?; + Ok(()) +} + +mod partial_tries { + use super::*; + + #[test] + fn lmdb_scans_from_n_leaf_partial_trie_had_expected_results() { + for generator in &TEST_TRIE_GENERATORS { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = generator().unwrap(); + let context = LmdbTestContext::new(&tries).unwrap(); + + for leaf in TEST_LEAVES.iter() { + let leaf_bytes = leaf.to_bytes().unwrap(); + check_scan::<_, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &root_hash, + &leaf_bytes, + ) + .unwrap() + } + } + } + + #[test] + fn in_memory_scans_from_n_leaf_partial_trie_had_expected_results() { + for generator in &TEST_TRIE_GENERATORS { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = generator().unwrap(); + let context = InMemoryTestContext::new(&tries).unwrap(); + + for leaf in TEST_LEAVES.iter() { + let leaf_bytes = leaf.to_bytes().unwrap(); + check_scan::<_, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &root_hash, + &leaf_bytes, + ) + .unwrap() + } + } + } +} + +mod full_tries { + use super::*; + + #[test] + fn lmdb_scans_from_n_leaf_full_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let context = LmdbTestContext::new(EMPTY_HASHED_TEST_TRIES).unwrap(); + let mut states: Vec = Vec::new(); + + for (state_index, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let (root_hash, tries) = generator().unwrap(); + context.update(&tries).unwrap(); + states.push(root_hash); + + for state in &states[..state_index] { + for leaf in TEST_LEAVES.iter() { + let leaf_bytes = leaf.to_bytes().unwrap(); + check_scan::<_, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + state, + &leaf_bytes, + ) + .unwrap() + } + } + } + } + + #[test] + fn in_memory_scans_from_n_leaf_full_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let context = InMemoryTestContext::new(EMPTY_HASHED_TEST_TRIES).unwrap(); + let mut states: Vec = Vec::new(); + + for (state_index, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let (root_hash, tries) = generator().unwrap(); + context.update(&tries).unwrap(); + states.push(root_hash); + + for state in &states[..state_index] { + for leaf in TEST_LEAVES.iter() { + let leaf_bytes = leaf.to_bytes().unwrap(); + check_scan::<_, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + state, + &leaf_bytes, + ) + .unwrap() + } + } + } + } +} diff --git a/node/src/contract_storage/trie_store/operations/tests/write.rs b/node/src/contract_storage/trie_store/operations/tests/write.rs new file mode 100644 index 0000000000..ce3961209c --- /dev/null +++ b/node/src/contract_storage/trie_store/operations/tests/write.rs @@ -0,0 +1,657 @@ +use super::*; + +mod empty_tries { + use std::collections::HashMap; + + use super::*; + + #[test] + fn lmdb_non_colliding_writes_to_n_leaf_empty_trie_had_expected_results() { + for num_leaves in 1..=TEST_LEAVES_LENGTH { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = TEST_TRIE_GENERATORS[0]().unwrap(); + let context = LmdbTestContext::new(&tries).unwrap(); + let initial_states = vec![root_hash]; + + writes_to_n_leaf_empty_trie_had_expected_results::<_, _, _, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &initial_states, + &TEST_LEAVES_NON_COLLIDING[..num_leaves], + ) + .unwrap(); + } + } + + #[test] + fn in_memory_non_colliding_writes_to_n_leaf_empty_trie_had_expected_results() { + for num_leaves in 1..=TEST_LEAVES_LENGTH { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = TEST_TRIE_GENERATORS[0]().unwrap(); + let context = InMemoryTestContext::new(&tries).unwrap(); + let initial_states = vec![root_hash]; + + writes_to_n_leaf_empty_trie_had_expected_results::<_, _, _, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &initial_states, + &TEST_LEAVES_NON_COLLIDING[..num_leaves], + ) + .unwrap(); + } + } + + #[test] + fn lmdb_writes_to_n_leaf_empty_trie_had_expected_results() { + for num_leaves in 1..=TEST_LEAVES_LENGTH { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = TEST_TRIE_GENERATORS[0]().unwrap(); + let context = LmdbTestContext::new(&tries).unwrap(); + let initial_states = vec![root_hash]; + + writes_to_n_leaf_empty_trie_had_expected_results::<_, _, _, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &initial_states, + &TEST_LEAVES[..num_leaves], + ) + .unwrap(); + } + } + + #[test] + fn in_memory_writes_to_n_leaf_empty_trie_had_expected_results() { + for num_leaves in 1..=TEST_LEAVES_LENGTH { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = TEST_TRIE_GENERATORS[0]().unwrap(); + let context = InMemoryTestContext::new(&tries).unwrap(); + let initial_states = vec![root_hash]; + + writes_to_n_leaf_empty_trie_had_expected_results::<_, _, _, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &initial_states, + &TEST_LEAVES[..num_leaves], + ) + .unwrap(); + } + } + + #[test] + fn in_memory_writes_to_n_leaf_empty_trie_had_expected_store_contents() { + let expected_contents: HashMap = { + let mut ret = HashMap::new(); + for generator in &TEST_TRIE_GENERATORS { + let (_, tries) = generator().unwrap(); + for HashedTestTrie { hash, trie } in tries { + ret.insert(hash, trie); + } + } + ret + }; + + let actual_contents: HashMap = { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = TEST_TRIE_GENERATORS[0]().unwrap(); + let context = InMemoryTestContext::new(&tries).unwrap(); + + write_leaves::<_, _, _, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &root_hash, + &TEST_LEAVES, + ) + .unwrap(); + + context.environment.dump(None).unwrap() + }; + + assert_eq!(expected_contents, actual_contents) + } +} + +mod partial_tries { + use super::*; + + fn noop_writes_to_n_leaf_partial_trie_had_expected_results<'a, R, S, E>( + correlation_id: CorrelationId, + environment: &'a R, + store: &S, + states: &[Blake2bHash], + num_leaves: usize, + ) -> Result<(), E> + where + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, + { + // Check that the expected set of leaves is in the trie + check_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + &states[0], + &TEST_LEAVES[..num_leaves], + &[], + )?; + + // Rewrite that set of leaves + let write_results = write_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + &states[0], + &TEST_LEAVES[..num_leaves], + )?; + + assert!(write_results + .iter() + .all(|result| *result == WriteResult::AlreadyExists)); + + // Check that the expected set of leaves is in the trie + check_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + &states[0], + &TEST_LEAVES[..num_leaves], + &[], + ) + } + + #[test] + fn lmdb_noop_writes_to_n_leaf_partial_trie_had_expected_results() { + for (num_leaves, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = generator().unwrap(); + let context = LmdbTestContext::new(&tries).unwrap(); + let states = vec![root_hash]; + + noop_writes_to_n_leaf_partial_trie_had_expected_results::<_, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &states, + num_leaves, + ) + .unwrap() + } + } + + #[test] + fn in_memory_noop_writes_to_n_leaf_partial_trie_had_expected_results() { + for (num_leaves, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = generator().unwrap(); + let context = InMemoryTestContext::new(&tries).unwrap(); + let states = vec![root_hash]; + + noop_writes_to_n_leaf_partial_trie_had_expected_results::<_, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &states, + num_leaves, + ) + .unwrap(); + } + } + + fn update_writes_to_n_leaf_partial_trie_had_expected_results<'a, R, S, E>( + correlation_id: CorrelationId, + environment: &'a R, + store: &S, + states: &[Blake2bHash], + num_leaves: usize, + ) -> Result<(), E> + where + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, + { + let mut states = states.to_owned(); + + // Check that the expected set of leaves is in the trie + check_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + &states[0], + &TEST_LEAVES[..num_leaves], + &[], + )?; + + // Update and check leaves + for (n, leaf) in TEST_LEAVES_UPDATED[..num_leaves].iter().enumerate() { + let expected_leaves: Vec = { + let n = n + 1; + TEST_LEAVES_UPDATED[..n] + .iter() + .chain(&TEST_LEAVES[n..num_leaves]) + .map(ToOwned::to_owned) + .collect() + }; + + let root_hash = { + let current_root = states.last().unwrap(); + let results = write_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + ¤t_root, + &[leaf.to_owned()], + )?; + assert_eq!(1, results.len()); + match results[0] { + WriteResult::Written(root_hash) => root_hash, + _ => panic!("value not written"), + } + }; + + states.push(root_hash); + + // Check that the expected set of leaves is in the trie + check_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + states.last().unwrap(), + &expected_leaves, + &[], + )?; + } + + Ok(()) + } + + #[test] + fn lmdb_update_writes_to_n_leaf_partial_trie_had_expected_results() { + for (num_leaves, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = generator().unwrap(); + let context = LmdbTestContext::new(&tries).unwrap(); + let initial_states = vec![root_hash]; + + update_writes_to_n_leaf_partial_trie_had_expected_results::<_, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &initial_states, + num_leaves, + ) + .unwrap() + } + } + + #[test] + fn in_memory_update_writes_to_n_leaf_partial_trie_had_expected_results() { + for (num_leaves, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let correlation_id = CorrelationId::new(); + let (root_hash, tries) = generator().unwrap(); + let context = InMemoryTestContext::new(&tries).unwrap(); + let states = vec![root_hash]; + + update_writes_to_n_leaf_partial_trie_had_expected_results::<_, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &states, + num_leaves, + ) + .unwrap() + } + } +} + +mod full_tries { + use super::*; + + fn noop_writes_to_n_leaf_full_trie_had_expected_results<'a, R, S, E>( + correlation_id: CorrelationId, + environment: &'a R, + store: &S, + states: &[Blake2bHash], + index: usize, + ) -> Result<(), E> + where + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, + { + // Check that the expected set of leaves is in the trie at every state reference + for (num_leaves, state) in states[..index].iter().enumerate() { + check_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + state, + &TEST_LEAVES[..num_leaves], + &[], + )?; + } + + // Rewrite that set of leaves + let write_results = write_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + states.last().unwrap(), + &TEST_LEAVES[..index], + )?; + + assert!(write_results + .iter() + .all(|result| *result == WriteResult::AlreadyExists)); + + // Check that the expected set of leaves is in the trie at every state reference + for (num_leaves, state) in states[..index].iter().enumerate() { + check_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + state, + &TEST_LEAVES[..num_leaves], + &[], + )? + } + + Ok(()) + } + + #[test] + fn lmdb_noop_writes_to_n_leaf_full_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let context = LmdbTestContext::new(EMPTY_HASHED_TEST_TRIES).unwrap(); + let mut states: Vec = Vec::new(); + + for (index, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let (root_hash, tries) = generator().unwrap(); + context.update(&tries).unwrap(); + states.push(root_hash); + + noop_writes_to_n_leaf_full_trie_had_expected_results::<_, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &states, + index, + ) + .unwrap(); + } + } + + #[test] + fn in_memory_noop_writes_to_n_leaf_full_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let context = InMemoryTestContext::new(EMPTY_HASHED_TEST_TRIES).unwrap(); + let mut states: Vec = Vec::new(); + + for (index, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let (root_hash, tries) = generator().unwrap(); + context.update(&tries).unwrap(); + states.push(root_hash); + + noop_writes_to_n_leaf_full_trie_had_expected_results::<_, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &states, + index, + ) + .unwrap(); + } + } + + fn update_writes_to_n_leaf_full_trie_had_expected_results<'a, R, S, E>( + correlation_id: CorrelationId, + environment: &'a R, + store: &S, + states: &[Blake2bHash], + num_leaves: usize, + ) -> Result<(), E> + where + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, + { + let mut states = states.to_vec(); + + // Check that the expected set of leaves is in the trie at every state reference + for (state_index, state) in states.iter().enumerate() { + check_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + state, + &TEST_LEAVES[..state_index], + &[], + )?; + } + + // Write set of leaves to the trie + let hashes = write_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + states.last().unwrap(), + &TEST_LEAVES_UPDATED[..num_leaves], + )? + .iter() + .map(|result| match result { + WriteResult::Written(root_hash) => *root_hash, + _ => panic!("write_leaves resulted in non-write"), + }) + .collect::>(); + + states.extend(hashes); + + let expected: Vec> = { + let mut ret = vec![vec![]]; + if num_leaves > 0 { + for i in 1..=num_leaves { + ret.push(TEST_LEAVES[..i].to_vec()) + } + for i in 1..=num_leaves { + ret.push( + TEST_LEAVES[i..num_leaves] + .iter() + .chain(&TEST_LEAVES_UPDATED[..i]) + .map(ToOwned::to_owned) + .collect::>(), + ) + } + } + ret + }; + + assert_eq!(states.len(), expected.len()); + + // Check that the expected set of leaves is in the trie at every state reference + for (state_index, state) in states.iter().enumerate() { + check_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + state, + &expected[state_index], + &[], + )?; + } + + Ok(()) + } + + #[test] + fn lmdb_update_writes_to_n_leaf_full_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let context = LmdbTestContext::new(EMPTY_HASHED_TEST_TRIES).unwrap(); + let mut states: Vec = Vec::new(); + + for (num_leaves, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let (root_hash, tries) = generator().unwrap(); + context.update(&tries).unwrap(); + states.push(root_hash); + + update_writes_to_n_leaf_full_trie_had_expected_results::<_, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &states, + num_leaves, + ) + .unwrap() + } + } + + #[test] + fn in_memory_update_writes_to_n_leaf_full_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let context = InMemoryTestContext::new(EMPTY_HASHED_TEST_TRIES).unwrap(); + let mut states: Vec = Vec::new(); + + for (num_leaves, generator) in TEST_TRIE_GENERATORS.iter().enumerate() { + let (root_hash, tries) = generator().unwrap(); + context.update(&tries).unwrap(); + states.push(root_hash); + + update_writes_to_n_leaf_full_trie_had_expected_results::<_, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &states, + num_leaves, + ) + .unwrap() + } + } + + fn node_writes_to_5_leaf_full_trie_had_expected_results<'a, R, S, E>( + correlation_id: CorrelationId, + environment: &'a R, + store: &S, + states: &[Blake2bHash], + ) -> Result<(), E> + where + R: TransactionSource<'a, Handle = S::Handle>, + S: TrieStore, + S::Error: From, + E: From + From + From, + { + let mut states = states.to_vec(); + let num_leaves = TEST_LEAVES_LENGTH; + + // Check that the expected set of leaves is in the trie at every state reference + for (state_index, state) in states.iter().enumerate() { + check_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + state, + &TEST_LEAVES[..state_index], + &[], + )?; + } + + // Write set of leaves to the trie + let hashes = write_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + states.last().unwrap(), + &TEST_LEAVES_ADJACENTS, + )? + .iter() + .map(|result| match result { + WriteResult::Written(root_hash) => *root_hash, + _ => panic!("write_leaves resulted in non-write"), + }) + .collect::>(); + + states.extend(hashes); + + let expected: Vec> = { + let mut ret = vec![vec![]]; + if num_leaves > 0 { + for i in 1..=num_leaves { + ret.push(TEST_LEAVES[..i].to_vec()) + } + for i in 1..=num_leaves { + ret.push( + TEST_LEAVES + .iter() + .chain(&TEST_LEAVES_ADJACENTS[..i]) + .map(ToOwned::to_owned) + .collect::>(), + ) + } + } + ret + }; + + assert_eq!(states.len(), expected.len()); + + // Check that the expected set of leaves is in the trie at every state reference + for (state_index, state) in states.iter().enumerate() { + check_leaves::<_, _, _, _, E>( + correlation_id, + environment, + store, + state, + &expected[state_index], + &[], + )?; + } + Ok(()) + } + + #[test] + fn lmdb_node_writes_to_5_leaf_full_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let context = LmdbTestContext::new(EMPTY_HASHED_TEST_TRIES).unwrap(); + let mut states: Vec = Vec::new(); + + for generator in &TEST_TRIE_GENERATORS { + let (root_hash, tries) = generator().unwrap(); + context.update(&tries).unwrap(); + states.push(root_hash); + } + + node_writes_to_5_leaf_full_trie_had_expected_results::<_, _, error::Error>( + correlation_id, + &context.environment, + &context.store, + &states, + ) + .unwrap() + } + + #[test] + fn in_memory_node_writes_to_5_leaf_full_trie_had_expected_results() { + let correlation_id = CorrelationId::new(); + let context = InMemoryTestContext::new(EMPTY_HASHED_TEST_TRIES).unwrap(); + let mut states: Vec = Vec::new(); + + for generator in &TEST_TRIE_GENERATORS { + let (root_hash, tries) = generator().unwrap(); + context.update(&tries).unwrap(); + states.push(root_hash); + } + + node_writes_to_5_leaf_full_trie_had_expected_results::<_, _, in_memory::Error>( + correlation_id, + &context.environment, + &context.store, + &states, + ) + .unwrap() + } +} diff --git a/node/src/contract_storage/trie_store/tests/concurrent.rs b/node/src/contract_storage/trie_store/tests/concurrent.rs new file mode 100644 index 0000000000..24becf0a84 --- /dev/null +++ b/node/src/contract_storage/trie_store/tests/concurrent.rs @@ -0,0 +1,120 @@ +use std::{ + sync::{Arc, Barrier}, + thread, +}; + +use tempfile::tempdir; + +use super::TestData; +use crate::contract_storage::{ + store::Store, + transaction_source::{ + in_memory::InMemoryEnvironment, lmdb::LmdbEnvironment, Transaction, TransactionSource, + }, + trie::Trie, + trie_store::{in_memory::InMemoryTrieStore, lmdb::LmdbTrieStore}, + TEST_MAP_SIZE, +}; + +#[test] +fn lmdb_writer_mutex_does_not_collide_with_readers() { + let dir = tempdir().unwrap(); + let env = Arc::new(LmdbEnvironment::new(&dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap()); + let store = Arc::new(LmdbTrieStore::new(&env, None, Default::default()).unwrap()); + let num_threads = 10; + let barrier = Arc::new(Barrier::new(num_threads + 1)); + let mut handles = Vec::new(); + let TestData(ref leaf_1_hash, ref leaf_1) = &super::create_data()[0..1][0]; + + for _ in 0..num_threads { + let reader_env = env.clone(); + let reader_store = store.clone(); + let reader_barrier = barrier.clone(); + let leaf_1_hash = *leaf_1_hash; + #[allow(clippy::clone_on_copy)] + let leaf_1 = leaf_1.clone(); + + handles.push(thread::spawn(move || { + { + let txn = reader_env.create_read_txn().unwrap(); + let result: Option, Vec>> = + reader_store.get(&txn, &leaf_1_hash).unwrap(); + assert_eq!(result, None); + txn.commit().unwrap(); + } + // wait for other reader threads to read and the main thread to + // take a read-write transaction + reader_barrier.wait(); + // wait for main thread to put and commit + reader_barrier.wait(); + { + let txn = reader_env.create_read_txn().unwrap(); + let result: Option, Vec>> = + reader_store.get(&txn, &leaf_1_hash).unwrap(); + txn.commit().unwrap(); + result.unwrap() == leaf_1 + } + })); + } + + let mut txn = env.create_read_write_txn().unwrap(); + // wait for reader threads to read + barrier.wait(); + store.put(&mut txn, &leaf_1_hash, &leaf_1).unwrap(); + txn.commit().unwrap(); + // sync with reader threads + barrier.wait(); + + assert!(handles.into_iter().all(|b| b.join().unwrap())) +} + +#[test] +fn in_memory_writer_mutex_does_not_collide_with_readers() { + let env = Arc::new(InMemoryEnvironment::new()); + let store = Arc::new(InMemoryTrieStore::new(&env, None)); + let num_threads = 10; + let barrier = Arc::new(Barrier::new(num_threads + 1)); + let mut handles = Vec::new(); + let TestData(ref leaf_1_hash, ref leaf_1) = &super::create_data()[0..1][0]; + + for _ in 0..num_threads { + let reader_env = env.clone(); + let reader_store = store.clone(); + let reader_barrier = barrier.clone(); + let leaf_1_hash = *leaf_1_hash; + #[allow(clippy::clone_on_copy)] + let leaf_1 = leaf_1.clone(); + + handles.push(thread::spawn(move || { + { + let txn = reader_env.create_read_txn().unwrap(); + let result: Option, Vec>> = + reader_store.get(&txn, &leaf_1_hash).unwrap(); + assert_eq!(result, None); + txn.commit().unwrap(); + } + // wait for other reader threads to read and the main thread to + // take a read-write transaction + reader_barrier.wait(); + // wait for main thread to put and commit + reader_barrier.wait(); + { + let txn = reader_env.create_read_txn().unwrap(); + let result: Option, Vec>> = + reader_store.get(&txn, &leaf_1_hash).unwrap(); + txn.commit().unwrap(); + result.unwrap() == leaf_1 + } + })); + } + + let mut txn = env.create_read_write_txn().unwrap(); + // wait for reader threads to read + barrier.wait(); + store.put(&mut txn, &leaf_1_hash, &leaf_1).unwrap(); + txn.commit().unwrap(); + // sync with reader threads + barrier.wait(); + + assert!(handles.into_iter().all(|b| b.join().unwrap())) +} diff --git a/node/src/contract_storage/trie_store/tests/mod.rs b/node/src/contract_storage/trie_store/tests/mod.rs new file mode 100644 index 0000000000..ab0ac380f9 --- /dev/null +++ b/node/src/contract_storage/trie_store/tests/mod.rs @@ -0,0 +1,73 @@ +mod concurrent; +mod proptests; +mod simple; + +use crate::contract_shared::newtypes::Blake2bHash; +use types::bytesrepr::ToBytes; + +use crate::contract_storage::trie::{Pointer, PointerBlock, Trie}; + +#[derive(Clone)] +struct TestData(Blake2bHash, Trie); + +impl<'a, K, V> Into<(&'a Blake2bHash, &'a Trie)> for &'a TestData { + fn into(self) -> (&'a Blake2bHash, &'a Trie) { + (&self.0, &self.1) + } +} + +fn create_data() -> Vec, Vec>> { + let leaf_1 = Trie::Leaf { + key: vec![0u8, 0, 0], + value: b"val_1".to_vec(), + }; + let leaf_2 = Trie::Leaf { + key: vec![1u8, 0, 0], + value: b"val_2".to_vec(), + }; + let leaf_3 = Trie::Leaf { + key: vec![1u8, 0, 1], + value: b"val_3".to_vec(), + }; + + let leaf_1_hash = Blake2bHash::new(&leaf_1.to_bytes().unwrap()); + let leaf_2_hash = Blake2bHash::new(&leaf_2.to_bytes().unwrap()); + let leaf_3_hash = Blake2bHash::new(&leaf_3.to_bytes().unwrap()); + + let node_2: Trie, Vec> = { + let mut pointer_block = PointerBlock::new(); + pointer_block[0] = Some(Pointer::LeafPointer(leaf_2_hash)); + pointer_block[1] = Some(Pointer::LeafPointer(leaf_3_hash)); + let pointer_block = Box::new(pointer_block); + Trie::Node { pointer_block } + }; + + let node_2_hash = Blake2bHash::new(&node_2.to_bytes().unwrap()); + + let ext_node: Trie, Vec> = { + let affix = vec![1u8, 0]; + let pointer = Pointer::NodePointer(node_2_hash); + Trie::Extension { affix, pointer } + }; + + let ext_node_hash = Blake2bHash::new(&ext_node.to_bytes().unwrap()); + + let node_1: Trie, Vec> = { + let mut pointer_block = PointerBlock::new(); + pointer_block[0] = Some(Pointer::LeafPointer(leaf_1_hash)); + pointer_block[1] = Some(Pointer::NodePointer(ext_node_hash)); + let pointer_block = Box::new(pointer_block); + Trie::Node { pointer_block } + }; + + let node_1_hash = Blake2bHash::new(&node_1.to_bytes().unwrap()); + + vec![ + TestData(leaf_1_hash, leaf_1), + TestData(leaf_2_hash, leaf_2), + TestData(leaf_3_hash, leaf_3), + TestData(node_1_hash, node_1), + TestData(node_2_hash, node_2), + TestData(ext_node_hash, ext_node), + ] +} diff --git a/node/src/contract_storage/trie_store/tests/proptests.rs b/node/src/contract_storage/trie_store/tests/proptests.rs new file mode 100644 index 0000000000..fc518eba3c --- /dev/null +++ b/node/src/contract_storage/trie_store/tests/proptests.rs @@ -0,0 +1,76 @@ +use std::ops::RangeInclusive; + +use lmdb::DatabaseFlags; +use proptest::{collection::vec, prelude::proptest}; +use tempfile::tempdir; + +use crate::contract_shared::{newtypes::Blake2bHash, stored_value::StoredValue}; +use types::{bytesrepr::ToBytes, Key}; + +use crate::contract_storage::{ + store::tests as store_tests, + trie::{gens::trie_arb, Trie}, + TEST_MAP_SIZE, +}; +use std::collections::BTreeMap; + +const DEFAULT_MIN_LENGTH: usize = 1; +const DEFAULT_MAX_LENGTH: usize = 4; + +fn get_range() -> RangeInclusive { + let start = option_env!("CL_TRIE_STORE_TEST_VECTOR_MIN_LENGTH") + .and_then(|s| str::parse::(s).ok()) + .unwrap_or(DEFAULT_MIN_LENGTH); + let end = option_env!("CL_TRIE_STORE_TEST_VECTOR_MAX_LENGTH") + .and_then(|s| str::parse::(s).ok()) + .unwrap_or(DEFAULT_MAX_LENGTH); + RangeInclusive::new(start, end) +} + +fn in_memory_roundtrip_succeeds(inputs: Vec>) -> bool { + use crate::contract_storage::{ + transaction_source::in_memory::InMemoryEnvironment, + trie_store::in_memory::InMemoryTrieStore, + }; + + let env = InMemoryEnvironment::new(); + let store = InMemoryTrieStore::new(&env, None); + + let inputs: BTreeMap> = inputs + .into_iter() + .map(|trie| (Blake2bHash::new(&trie.to_bytes().unwrap()), trie)) + .collect(); + + store_tests::roundtrip_succeeds(&env, &store, inputs).unwrap() +} + +fn lmdb_roundtrip_succeeds(inputs: Vec>) -> bool { + use crate::contract_storage::{ + transaction_source::lmdb::LmdbEnvironment, trie_store::lmdb::LmdbTrieStore, + }; + + let tmp_dir = tempdir().unwrap(); + let env = LmdbEnvironment::new(&tmp_dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + let store = LmdbTrieStore::new(&env, None, DatabaseFlags::empty()).unwrap(); + + let inputs: BTreeMap> = inputs + .into_iter() + .map(|trie| (Blake2bHash::new(&trie.to_bytes().unwrap()), trie)) + .collect(); + + let ret = store_tests::roundtrip_succeeds(&env, &store, inputs).unwrap(); + tmp_dir.close().unwrap(); + ret +} + +proptest! { + #[test] + fn prop_in_memory_roundtrip_succeeds(v in vec(trie_arb(), get_range())) { + assert!(in_memory_roundtrip_succeeds(v)) + } + + #[test] + fn prop_lmdb_roundtrip_succeeds(v in vec(trie_arb(), get_range())) { + assert!(lmdb_roundtrip_succeeds(v)) + } +} diff --git a/node/src/contract_storage/trie_store/tests/simple.rs b/node/src/contract_storage/trie_store/tests/simple.rs new file mode 100644 index 0000000000..b56989bfae --- /dev/null +++ b/node/src/contract_storage/trie_store/tests/simple.rs @@ -0,0 +1,552 @@ +use lmdb::DatabaseFlags; +use tempfile::tempdir; + +use types::bytesrepr::{FromBytes, ToBytes}; + +use super::TestData; +use crate::contract_storage::{ + error::{self, in_memory}, + store::StoreExt, + transaction_source::{ + in_memory::InMemoryEnvironment, lmdb::LmdbEnvironment, Transaction, TransactionSource, + }, + trie::Trie, + trie_store::{in_memory::InMemoryTrieStore, lmdb::LmdbTrieStore, TrieStore}, + TEST_MAP_SIZE, +}; + +fn put_succeeds<'a, K, V, S, X, E>( + store: &S, + transaction_source: &'a X, + items: &[TestData], +) -> Result<(), E> +where + K: ToBytes, + V: ToBytes, + S: TrieStore, + X: TransactionSource<'a, Handle = S::Handle>, + S::Error: From, + E: From + From, +{ + let mut txn: X::ReadWriteTransaction = transaction_source.create_read_write_txn()?; + let items = items.iter().map(Into::into); + store.put_many(&mut txn, items)?; + txn.commit()?; + Ok(()) +} + +#[test] +fn in_memory_put_succeeds() { + let env = InMemoryEnvironment::new(); + let store = InMemoryTrieStore::new(&env, None); + let data = &super::create_data()[0..1]; + + assert!(put_succeeds::<_, _, _, _, in_memory::Error>(&store, &env, data).is_ok()); +} + +#[test] +fn lmdb_put_succeeds() { + let tmp_dir = tempdir().unwrap(); + let env = LmdbEnvironment::new(&tmp_dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + let store = LmdbTrieStore::new(&env, None, DatabaseFlags::empty()).unwrap(); + let data = &super::create_data()[0..1]; + + assert!(put_succeeds::<_, _, _, _, error::Error>(&store, &env, data).is_ok()); + + tmp_dir.close().unwrap(); +} + +fn put_get_succeeds<'a, K, V, S, X, E>( + store: &S, + transaction_source: &'a X, + items: &[TestData], +) -> Result>>, E> +where + K: ToBytes + FromBytes, + V: ToBytes + FromBytes, + S: TrieStore, + X: TransactionSource<'a, Handle = S::Handle>, + S::Error: From, + E: From + From, +{ + let mut txn: X::ReadWriteTransaction = transaction_source.create_read_write_txn()?; + let items = items.iter().map(Into::into); + store.put_many(&mut txn, items.clone())?; + let keys = items.map(|(k, _)| k); + let ret = store.get_many(&txn, keys)?; + txn.commit()?; + Ok(ret) +} + +#[test] +fn in_memory_put_get_succeeds() { + let env = InMemoryEnvironment::new(); + let store = InMemoryTrieStore::new(&env, None); + let data = &super::create_data()[0..1]; + + let expected: Vec, Vec>> = + data.to_vec().into_iter().map(|TestData(_, v)| v).collect(); + + assert_eq!( + expected, + put_get_succeeds::<_, _, _, _, in_memory::Error>(&store, &env, data) + .expect("put_get_succeeds failed") + .into_iter() + .collect::, Vec>>>>() + .expect("one of the outputs was empty") + ) +} + +#[test] +fn lmdb_put_get_succeeds() { + let tmp_dir = tempdir().unwrap(); + let env = LmdbEnvironment::new(&tmp_dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + let store = LmdbTrieStore::new(&env, None, DatabaseFlags::empty()).unwrap(); + let data = &super::create_data()[0..1]; + + let expected: Vec, Vec>> = + data.to_vec().into_iter().map(|TestData(_, v)| v).collect(); + + assert_eq!( + expected, + put_get_succeeds::<_, _, _, _, error::Error>(&store, &env, data) + .expect("put_get_succeeds failed") + .into_iter() + .collect::, Vec>>>>() + .expect("one of the outputs was empty") + ); + + tmp_dir.close().unwrap(); +} + +#[test] +fn in_memory_put_get_many_succeeds() { + let env = InMemoryEnvironment::new(); + let store = InMemoryTrieStore::new(&env, None); + let data = super::create_data(); + + let expected: Vec, Vec>> = + data.to_vec().into_iter().map(|TestData(_, v)| v).collect(); + + assert_eq!( + expected, + put_get_succeeds::<_, _, _, _, in_memory::Error>(&store, &env, &data) + .expect("put_get failed") + .into_iter() + .collect::, Vec>>>>() + .expect("one of the outputs was empty") + ) +} + +#[test] +fn lmdb_put_get_many_succeeds() { + let tmp_dir = tempdir().unwrap(); + let env = LmdbEnvironment::new(&tmp_dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + let store = LmdbTrieStore::new(&env, None, DatabaseFlags::empty()).unwrap(); + let data = super::create_data(); + + let expected: Vec, Vec>> = + data.to_vec().into_iter().map(|TestData(_, v)| v).collect(); + + assert_eq!( + expected, + put_get_succeeds::<_, _, _, _, error::Error>(&store, &env, &data) + .expect("put_get failed") + .into_iter() + .collect::, Vec>>>>() + .expect("one of the outputs was empty") + ); + + tmp_dir.close().unwrap(); +} + +fn uncommitted_read_write_txn_does_not_persist<'a, K, V, S, X, E>( + store: &S, + transaction_source: &'a X, + items: &[TestData], +) -> Result>>, E> +where + K: ToBytes + FromBytes, + V: ToBytes + FromBytes, + S: TrieStore, + X: TransactionSource<'a, Handle = S::Handle>, + S::Error: From, + E: From + From, +{ + { + let mut txn: X::ReadWriteTransaction = transaction_source.create_read_write_txn()?; + let items = items.iter().map(Into::into); + store.put_many(&mut txn, items)?; + } + { + let txn: X::ReadTransaction = transaction_source.create_read_txn()?; + let keys = items.iter().map(|TestData(k, _)| k); + let ret = store.get_many(&txn, keys)?; + txn.commit()?; + Ok(ret) + } +} + +#[test] +fn in_memory_uncommitted_read_write_txn_does_not_persist() { + let env = InMemoryEnvironment::new(); + let store = InMemoryTrieStore::new(&env, None); + let data = super::create_data(); + + assert_eq!( + None, + uncommitted_read_write_txn_does_not_persist::<_, _, _, _, in_memory::Error>( + &store, &env, &data, + ) + .expect("uncommitted_read_write_txn_does_not_persist failed") + .into_iter() + .collect::, Vec>>>>() + ) +} + +#[test] +fn lmdb_uncommitted_read_write_txn_does_not_persist() { + let tmp_dir = tempdir().unwrap(); + let env = LmdbEnvironment::new(&tmp_dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + let store = LmdbTrieStore::new(&env, None, DatabaseFlags::empty()).unwrap(); + let data = super::create_data(); + + assert_eq!( + None, + uncommitted_read_write_txn_does_not_persist::<_, _, _, _, error::Error>( + &store, &env, &data, + ) + .expect("uncommitted_read_write_txn_does_not_persist failed") + .into_iter() + .collect::, Vec>>>>() + ); + + tmp_dir.close().unwrap(); +} + +fn read_write_transaction_does_not_block_read_transaction<'a, X, E>( + transaction_source: &'a X, +) -> Result<(), E> +where + X: TransactionSource<'a>, + E: From, +{ + let read_write_txn = transaction_source.create_read_write_txn()?; + let read_txn = transaction_source.create_read_txn()?; + read_write_txn.commit()?; + read_txn.commit()?; + Ok(()) +} + +#[test] +fn in_memory_read_write_transaction_does_not_block_read_transaction() { + let env = InMemoryEnvironment::new(); + + assert!( + read_write_transaction_does_not_block_read_transaction::<_, in_memory::Error>(&env).is_ok() + ) +} + +#[test] +fn lmdb_read_write_transaction_does_not_block_read_transaction() { + let dir = tempdir().unwrap(); + let env = LmdbEnvironment::new(&dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + + assert!(read_write_transaction_does_not_block_read_transaction::<_, error::Error>(&env).is_ok()) +} + +fn reads_are_isolated<'a, S, X, E>(store: &S, env: &'a X) -> Result<(), E> +where + S: TrieStore, Vec>, + X: TransactionSource<'a, Handle = S::Handle>, + S::Error: From, + E: From + From + From, +{ + let TestData(leaf_1_hash, leaf_1) = &super::create_data()[0..1][0]; + + { + let read_txn_1 = env.create_read_txn()?; + let result = store.get(&read_txn_1, &leaf_1_hash)?; + assert_eq!(result, None); + + { + let mut write_txn = env.create_read_write_txn()?; + store.put(&mut write_txn, &leaf_1_hash, &leaf_1)?; + write_txn.commit()?; + } + + let result = store.get(&read_txn_1, &leaf_1_hash)?; + read_txn_1.commit()?; + assert_eq!(result, None); + } + + { + let read_txn_2 = env.create_read_txn()?; + let result = store.get(&read_txn_2, &leaf_1_hash)?; + read_txn_2.commit()?; + assert_eq!(result, Some(leaf_1.to_owned())); + } + + Ok(()) +} + +#[test] +fn in_memory_reads_are_isolated() { + let env = InMemoryEnvironment::new(); + let store = InMemoryTrieStore::new(&env, None); + + assert!(reads_are_isolated::<_, _, in_memory::Error>(&store, &env).is_ok()) +} + +#[test] +fn lmdb_reads_are_isolated() { + let dir = tempdir().unwrap(); + let env = LmdbEnvironment::new(&dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + let store = LmdbTrieStore::new(&env, None, DatabaseFlags::empty()).unwrap(); + + assert!(reads_are_isolated::<_, _, error::Error>(&store, &env).is_ok()) +} + +fn reads_are_isolated_2<'a, S, X, E>(store: &S, env: &'a X) -> Result<(), E> +where + S: TrieStore, Vec>, + X: TransactionSource<'a, Handle = S::Handle>, + S::Error: From, + E: From + From + From, +{ + let data = super::create_data(); + let TestData(ref leaf_1_hash, ref leaf_1) = data[0]; + let TestData(ref leaf_2_hash, ref leaf_2) = data[1]; + + { + let mut write_txn = env.create_read_write_txn()?; + store.put(&mut write_txn, leaf_1_hash, leaf_1)?; + write_txn.commit()?; + } + + { + let read_txn_1 = env.create_read_txn()?; + { + let mut write_txn = env.create_read_write_txn()?; + store.put(&mut write_txn, leaf_2_hash, leaf_2)?; + write_txn.commit()?; + } + let result = store.get(&read_txn_1, leaf_1_hash)?; + read_txn_1.commit()?; + assert_eq!(result, Some(leaf_1.to_owned())); + } + + { + let read_txn_2 = env.create_read_txn()?; + let result = store.get(&read_txn_2, leaf_2_hash)?; + read_txn_2.commit()?; + assert_eq!(result, Some(leaf_2.to_owned())); + } + + Ok(()) +} + +#[test] +fn in_memory_reads_are_isolated_2() { + let env = InMemoryEnvironment::new(); + let store = InMemoryTrieStore::new(&env, None); + + assert!(reads_are_isolated_2::<_, _, in_memory::Error>(&store, &env).is_ok()) +} + +#[test] +fn lmdb_reads_are_isolated_2() { + let dir = tempdir().unwrap(); + let env = LmdbEnvironment::new(&dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + let store = LmdbTrieStore::new(&env, None, DatabaseFlags::empty()).unwrap(); + + assert!(reads_are_isolated_2::<_, _, error::Error>(&store, &env).is_ok()) +} + +fn dbs_are_isolated<'a, S, X, E>(env: &'a X, store_a: &S, store_b: &S) -> Result<(), E> +where + S: TrieStore, Vec>, + X: TransactionSource<'a, Handle = S::Handle>, + S::Error: From, + E: From + From + From, +{ + let data = super::create_data(); + let TestData(ref leaf_1_hash, ref leaf_1) = data[0]; + let TestData(ref leaf_2_hash, ref leaf_2) = data[1]; + + { + let mut write_txn = env.create_read_write_txn()?; + store_a.put(&mut write_txn, leaf_1_hash, leaf_1)?; + write_txn.commit()?; + } + + { + let mut write_txn = env.create_read_write_txn()?; + store_b.put(&mut write_txn, leaf_2_hash, leaf_2)?; + write_txn.commit()?; + } + + { + let read_txn = env.create_read_txn()?; + let result = store_a.get(&read_txn, leaf_1_hash)?; + assert_eq!(result, Some(leaf_1.to_owned())); + let result = store_a.get(&read_txn, leaf_2_hash)?; + assert_eq!(result, None); + read_txn.commit()?; + } + + { + let read_txn = env.create_read_txn()?; + let result = store_b.get(&read_txn, leaf_1_hash)?; + assert_eq!(result, None); + let result = store_b.get(&read_txn, leaf_2_hash)?; + assert_eq!(result, Some(leaf_2.to_owned())); + read_txn.commit()?; + } + + Ok(()) +} + +#[test] +fn in_memory_dbs_are_isolated() { + let env = InMemoryEnvironment::new(); + let store_a = InMemoryTrieStore::new(&env, Some("a")); + let store_b = InMemoryTrieStore::new(&env, Some("b")); + + assert!(dbs_are_isolated::<_, _, in_memory::Error>(&env, &store_a, &store_b).is_ok()) +} + +#[test] +fn lmdb_dbs_are_isolated() { + let dir = tempdir().unwrap(); + let env = LmdbEnvironment::new(&dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + let store_a = LmdbTrieStore::new(&env, Some("a"), DatabaseFlags::empty()).unwrap(); + let store_b = LmdbTrieStore::new(&env, Some("b"), DatabaseFlags::empty()).unwrap(); + + assert!(dbs_are_isolated::<_, _, error::Error>(&env, &store_a, &store_b).is_ok()) +} + +fn transactions_can_be_used_across_sub_databases<'a, S, X, E>( + env: &'a X, + store_a: &S, + store_b: &S, +) -> Result<(), E> +where + S: TrieStore, Vec>, + X: TransactionSource<'a, Handle = S::Handle>, + S::Error: From, + E: From + From + From, +{ + let data = super::create_data(); + let TestData(ref leaf_1_hash, ref leaf_1) = data[0]; + let TestData(ref leaf_2_hash, ref leaf_2) = data[1]; + + { + let mut write_txn = env.create_read_write_txn()?; + store_a.put(&mut write_txn, leaf_1_hash, leaf_1)?; + store_b.put(&mut write_txn, leaf_2_hash, leaf_2)?; + write_txn.commit()?; + } + + { + let read_txn = env.create_read_txn()?; + let result = store_a.get(&read_txn, leaf_1_hash)?; + assert_eq!(result, Some(leaf_1.to_owned())); + let result = store_b.get(&read_txn, leaf_2_hash)?; + assert_eq!(result, Some(leaf_2.to_owned())); + read_txn.commit()?; + } + + Ok(()) +} + +#[test] +fn in_memory_transactions_can_be_used_across_sub_databases() { + let env = InMemoryEnvironment::new(); + let store_a = InMemoryTrieStore::new(&env, Some("a")); + let store_b = InMemoryTrieStore::new(&env, Some("b")); + + assert!( + transactions_can_be_used_across_sub_databases::<_, _, in_memory::Error>( + &env, &store_a, &store_b, + ) + .is_ok() + ); +} + +#[test] +fn lmdb_transactions_can_be_used_across_sub_databases() { + let dir = tempdir().unwrap(); + let env = LmdbEnvironment::new(&dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + let store_a = LmdbTrieStore::new(&env, Some("a"), DatabaseFlags::empty()).unwrap(); + let store_b = LmdbTrieStore::new(&env, Some("b"), DatabaseFlags::empty()).unwrap(); + + assert!( + transactions_can_be_used_across_sub_databases::<_, _, error::Error>( + &env, &store_a, &store_b, + ) + .is_ok() + ) +} + +fn uncommitted_transactions_across_sub_databases_do_not_persist<'a, S, X, E>( + env: &'a X, + store_a: &S, + store_b: &S, +) -> Result<(), E> +where + S: TrieStore, Vec>, + X: TransactionSource<'a, Handle = S::Handle>, + S::Error: From, + E: From + From + From, +{ + let data = super::create_data(); + let TestData(ref leaf_1_hash, ref leaf_1) = data[0]; + let TestData(ref leaf_2_hash, ref leaf_2) = data[1]; + + { + let mut write_txn = env.create_read_write_txn()?; + store_a.put(&mut write_txn, leaf_1_hash, leaf_1)?; + store_b.put(&mut write_txn, leaf_2_hash, leaf_2)?; + } + + { + let read_txn = env.create_read_txn()?; + let result = store_a.get(&read_txn, leaf_1_hash)?; + assert_eq!(result, None); + let result = store_b.get(&read_txn, leaf_2_hash)?; + assert_eq!(result, None); + read_txn.commit()?; + } + + Ok(()) +} + +#[test] +fn in_memory_uncommitted_transactions_across_sub_databases_do_not_persist() { + let env = InMemoryEnvironment::new(); + let store_a = InMemoryTrieStore::new(&env, Some("a")); + let store_b = InMemoryTrieStore::new(&env, Some("b")); + + assert!( + uncommitted_transactions_across_sub_databases_do_not_persist::<_, _, in_memory::Error>( + &env, &store_a, &store_b, + ) + .is_ok() + ); +} + +#[test] +fn lmdb_uncommitted_transactions_across_sub_databases_do_not_persist() { + let dir = tempdir().unwrap(); + let env = LmdbEnvironment::new(&dir.path().to_path_buf(), *TEST_MAP_SIZE).unwrap(); + let store_a = LmdbTrieStore::new(&env, Some("a"), DatabaseFlags::empty()).unwrap(); + let store_b = LmdbTrieStore::new(&env, Some("b"), DatabaseFlags::empty()).unwrap(); + + assert!( + uncommitted_transactions_across_sub_databases_do_not_persist::<_, _, error::Error>( + &env, &store_a, &store_b, + ) + .is_ok() + ) +} diff --git a/src/crypto.rs b/node/src/crypto.rs similarity index 100% rename from src/crypto.rs rename to node/src/crypto.rs diff --git a/src/crypto/asymmetric_key.rs b/node/src/crypto/asymmetric_key.rs similarity index 100% rename from src/crypto/asymmetric_key.rs rename to node/src/crypto/asymmetric_key.rs diff --git a/src/crypto/error.rs b/node/src/crypto/error.rs similarity index 100% rename from src/crypto/error.rs rename to node/src/crypto/error.rs diff --git a/src/crypto/hash.rs b/node/src/crypto/hash.rs similarity index 100% rename from src/crypto/hash.rs rename to node/src/crypto/hash.rs diff --git a/src/effect.rs b/node/src/effect.rs similarity index 100% rename from src/effect.rs rename to node/src/effect.rs diff --git a/src/effect/announcements.rs b/node/src/effect/announcements.rs similarity index 100% rename from src/effect/announcements.rs rename to node/src/effect/announcements.rs diff --git a/src/effect/requests.rs b/node/src/effect/requests.rs similarity index 100% rename from src/effect/requests.rs rename to node/src/effect/requests.rs diff --git a/src/lib.rs b/node/src/lib.rs similarity index 95% rename from src/lib.rs rename to node/src/lib.rs index e6e9b6f6b8..f822c02e4e 100644 --- a/src/lib.rs +++ b/node/src/lib.rs @@ -18,7 +18,6 @@ missing_docs, trivial_casts, trivial_numeric_casts, - unreachable_pub, unused_qualifications )] // Clippy is a little too trigger happy with these types, resulting in a lot of unnecessary @@ -26,6 +25,9 @@ #![allow(clippy::type_complexity)] pub mod components; +pub mod contract_core; +pub mod contract_shared; +pub mod contract_storage; pub mod crypto; pub mod effect; pub mod logging; diff --git a/node/src/logging.rs b/node/src/logging.rs new file mode 100644 index 0000000000..2c7ed2e00f --- /dev/null +++ b/node/src/logging.rs @@ -0,0 +1,134 @@ +//! Logging via the tracing crate. + +use std::{fmt, io}; + +use ansi_term::{Color, Style}; +use tracing::{Event, Level, Subscriber}; +use tracing_subscriber::{ + fmt::{ + format, + time::{FormatTime, SystemTime}, + FmtContext, FormatEvent, FormatFields, FormattedFields, + }, + prelude::*, + registry::LookupSpan, + EnvFilter, +}; + +/// This is used to implement tracing's `FormatEvent` so that we can customize the way tracing +/// events are formatted. +struct FmtEvent {} + +impl FormatEvent for FmtEvent +where + S: Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + fn format_event( + &self, + ctx: &FmtContext<'_, S, N>, + writer: &mut dyn fmt::Write, + event: &Event<'_>, + ) -> fmt::Result { + // print the date/time with dimmed style + let dimmed = Style::new().dimmed(); + write!(writer, "{}", dimmed.prefix())?; + SystemTime.format_time(writer)?; + write!(writer, "{}", dimmed.suffix())?; + + // print the log level in color + let meta = event.metadata(); + let color = match *meta.level() { + Level::TRACE => Color::Purple, + Level::DEBUG => Color::Blue, + Level::INFO => Color::Green, + Level::WARN => Color::Yellow, + Level::ERROR => Color::Red, + }; + + write!( + writer, + " {}{:<6}{}", + color.prefix(), + meta.level().to_string(), + color.suffix() + )?; + + // print the span information as per + // https://github.com/tokio-rs/tracing/blob/21f28f74/tracing-subscriber/src/fmt/format/mod.rs#L667-L695 + let mut span_seen = false; + + ctx.visit_spans(|span| { + write!(writer, "{}", span.metadata().name())?; + span_seen = true; + + let ext = span.extensions(); + let fields = &ext + .get::>() + .expect("Unable to find FormattedFields in extensions; this is a bug"); + if !fields.is_empty() { + write!(writer, "{{{}}}", fields)?; + } + writer.write_char(':') + })?; + + if span_seen { + writer.write_char(' ')?; + } + + // print the module path, filename and line number with dimmed style + let module = meta.module_path().unwrap_or_default(); + + let file = meta + .file() + .unwrap_or_default() + .rsplitn(2, '/') + .next() + .unwrap_or_default(); + + let line = meta.line().unwrap_or_default(); + + write!( + writer, + "{}[{} {}:{}]{} ", + dimmed.prefix(), + module, + file, + line, + dimmed.suffix() + )?; + + // print the log message and other fields + ctx.format_fields(writer, event)?; + writeln!(writer) + } +} + +/// Initializes the logging system. +/// +/// This function should only be called once during the lifetime of the application. Do not call +/// this outside of the application or testing code, the installed logger is global. +/// +/// See the `README.md` for hints on how to configure logging at runtime. +pub fn init() -> anyhow::Result<()> { + let formatter = format::debug_fn(|writer, field, value| { + if field.name() == "message" { + write!(writer, "{:?}", value) + } else { + write!(writer, "{}={:?}", field, value) + } + }) + .delimited("; "); + + // Setup a new tracing-subscriber writing to `stderr` for logging. + tracing::subscriber::set_global_default( + tracing_subscriber::fmt() + .with_writer(io::stderr) + .with_env_filter(EnvFilter::from_default_env()) + .fmt_fields(formatter) + .event_format(FmtEvent {}) + .finish(), + )?; + + Ok(()) +} diff --git a/src/reactor.rs b/node/src/reactor.rs similarity index 100% rename from src/reactor.rs rename to node/src/reactor.rs diff --git a/src/reactor/non_validator.rs b/node/src/reactor/non_validator.rs similarity index 100% rename from src/reactor/non_validator.rs rename to node/src/reactor/non_validator.rs diff --git a/src/reactor/queue_kind.rs b/node/src/reactor/queue_kind.rs similarity index 100% rename from src/reactor/queue_kind.rs rename to node/src/reactor/queue_kind.rs diff --git a/src/reactor/validator.rs b/node/src/reactor/validator.rs similarity index 94% rename from src/reactor/validator.rs rename to node/src/reactor/validator.rs index 1c20327bcd..9b980fbbeb 100644 --- a/src/reactor/validator.rs +++ b/node/src/reactor/validator.rs @@ -17,6 +17,7 @@ use crate::{ components::{ api_server::{self, ApiServer}, consensus::{self, EraSupervisor}, + contract_runtime::{self, ContractRuntime}, deploy_gossiper::{self, DeployGossiper}, pinger::{self, Pinger}, storage::{Storage, StorageType}, @@ -90,6 +91,11 @@ pub enum Event { /// Network announcement. #[from] NetworkAnnouncement(NetworkAnnouncement), + + // Contract Runtime + /// Contract runtime event. + #[from] + ContractRuntime(contract_runtime::Event), } impl From for Event { @@ -132,6 +138,7 @@ pub struct Reactor { consensus: EraSupervisor, deploy_gossiper: DeployGossiper, rng: ChaCha20Rng, + contract_runtime: ContractRuntime, } impl reactor::Reactor for Reactor { @@ -153,6 +160,7 @@ impl reactor::Reactor for Reactor { let (api_server, api_server_effects) = ApiServer::new(cfg.http_server, effect_builder); let consensus = EraSupervisor::new(); let deploy_gossiper = DeployGossiper::new(cfg.gossip); + let (contract_runtime, _contract_runtime_effects) = ContractRuntime::new(effect_builder); let mut effects = reactor::wrap_effects(Event::Network, net_effects); effects.extend(reactor::wrap_effects(Event::Pinger, pinger_effects)); @@ -169,6 +177,7 @@ impl reactor::Reactor for Reactor { consensus, deploy_gossiper, rng, + contract_runtime, }, effects, )) @@ -239,6 +248,7 @@ impl reactor::Reactor for Reactor { // Any incoming message is one for the pinger. self.dispatch_event(effect_builder, reactor_event) } + Event::ContractRuntime(event) => todo!("handle contract runtime event: {:?}", event), } } } @@ -254,6 +264,7 @@ impl Display for Event { Event::DeployGossiper(event) => write!(f, "deploy gossiper: {}", event), Event::NetworkRequest(req) => write!(f, "network request: {}", req), Event::NetworkAnnouncement(ann) => write!(f, "network announcement: {}", ann), + Event::ContractRuntime(event) => write!(f, "contract runtime: {}", event), } } } diff --git a/src/reactor/validator/config.rs b/node/src/reactor/validator/config.rs similarity index 100% rename from src/reactor/validator/config.rs rename to node/src/reactor/validator/config.rs diff --git a/src/reactor/validator/error.rs b/node/src/reactor/validator/error.rs similarity index 100% rename from src/reactor/validator/error.rs rename to node/src/reactor/validator/error.rs diff --git a/src/testing.rs b/node/src/testing.rs similarity index 100% rename from src/testing.rs rename to node/src/testing.rs diff --git a/src/testing/network.rs b/node/src/testing/network.rs similarity index 100% rename from src/testing/network.rs rename to node/src/testing/network.rs diff --git a/src/tls.rs b/node/src/tls.rs similarity index 100% rename from src/tls.rs rename to node/src/tls.rs diff --git a/src/types.rs b/node/src/types.rs similarity index 100% rename from src/types.rs rename to node/src/types.rs diff --git a/src/types/block.rs b/node/src/types/block.rs similarity index 100% rename from src/types/block.rs rename to node/src/types/block.rs diff --git a/src/types/deploy.rs b/node/src/types/deploy.rs similarity index 100% rename from src/types/deploy.rs rename to node/src/types/deploy.rs diff --git a/src/utils.rs b/node/src/utils.rs similarity index 100% rename from src/utils.rs rename to node/src/utils.rs diff --git a/src/utils/gossip_table.rs b/node/src/utils/gossip_table.rs similarity index 100% rename from src/utils/gossip_table.rs rename to node/src/utils/gossip_table.rs diff --git a/src/utils/round_robin.rs b/node/src/utils/round_robin.rs similarity index 100% rename from src/utils/round_robin.rs rename to node/src/utils/round_robin.rs diff --git a/smart-contracts/contract-as/.gitignore b/smart-contracts/contract-as/.gitignore new file mode 100644 index 0000000000..119f029a04 --- /dev/null +++ b/smart-contracts/contract-as/.gitignore @@ -0,0 +1,99 @@ +temp-apidoc/ + +# Created by https://www.gitignore.io/api/node +# Edit at https://www.gitignore.io/?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# react / gatsby +public/ + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# End of https://www.gitignore.io/api/node diff --git a/smart-contracts/contract-as/.npmignore b/smart-contracts/contract-as/.npmignore new file mode 100644 index 0000000000..59536de018 --- /dev/null +++ b/smart-contracts/contract-as/.npmignore @@ -0,0 +1 @@ +temp-apidoc/ diff --git a/smart-contracts/contract-as/.npmrc b/smart-contracts/contract-as/.npmrc new file mode 100644 index 0000000000..5af8673616 --- /dev/null +++ b/smart-contracts/contract-as/.npmrc @@ -0,0 +1 @@ +unsafe-perm = true diff --git a/smart-contracts/contract-as/README.md b/smart-contracts/contract-as/README.md new file mode 100644 index 0000000000..7a2a2232e4 --- /dev/null +++ b/smart-contracts/contract-as/README.md @@ -0,0 +1,91 @@ +# @casperlabs/contract + +This package allows a distributed app developer to create smart contracts +for the open source [CasperLabs](https://github.com/CasperLabs/CasperLabs) project using [AssemblyScript](https://www.npmjs.com/package/assemblyscript). + +## Installation +For each smart contract you create, make a project directory and initialize it. +``` +mkdir project +cd project +npm init +``` + +npm init will prompt you for various details about your project; +answer as you see fit but you may safely default everything except `name` which should follow the convention of +`your-contract-name`. + +Then install assembly script and this package in the project directory. + +``` +npm install --save-dev assemblyscript@0.9.1 +npm install --save @casperlabs/contract +``` + +## Usage +Add script entries for assembly script to your project's `package.json`; note that your contract name is used +for the name of the wasm file. +``` +{ + "name": "your-contract-name", + ... + "scripts": { + "asbuild:optimized": "asc assembly/index.ts -b dist/your-contract-name.wasm --validate --optimize --use abort=", + "asbuild": "npm run asbuild:optimized", + ... + }, + ... +} +``` +In your project root, create an `index.js` file with the following contents: +```js +const fs = require("fs"); +​ +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/dist/your-contract-name.wasm")); +​ +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +​ +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); +``` + +Create an `assembly/tsconfig.json` file in the following way: +```json +{ + "extends": "../node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} +``` + +### Sample smart contract +Create a `assembly/index.ts` file. This is where the code for your contract will go. + +You can use the following sample snippet which demonstrates a very simple smart contract that immediately returns an error, which will write a message to a block if executed on the CasperLabs platform. + +```typescript +//@ts-nocheck +import {Error, ErrorCode} from "@casperlabs/contract/error"; + +// simplest possible feedback loop +export function call(): void { + Error.fromErrorCode(ErrorCode.None).revert(); // ErrorCode: 1 +} +``` +If you prefer a more complicated first contract, you can look at client contracts on the [CasperLabs](https://github.com/CasperLabs/CasperLabs/tree/master/execution-engine/contracts-as/client) github repository for inspiration. + +### Compile to wasm +To compile your contract to wasm, use npm to run the asbuild script from your project root. +``` +npm run asbuild +``` +If the build is successful, you should see a `dist` folder in your root folder and in it +should be `your-contract-name.wasm` diff --git a/smart-contracts/contract-as/assembly/account.ts b/smart-contracts/contract-as/assembly/account.ts new file mode 100644 index 0000000000..8935ee3b90 --- /dev/null +++ b/smart-contracts/contract-as/assembly/account.ts @@ -0,0 +1,181 @@ +import * as externals from "./externals"; +import {arrayToTyped} from "./utils"; +import {UREF_SERIALIZED_LENGTH} from "./constants"; +import {URef} from "./uref"; +import {AccountHash} from "./key"; + +/** + * Enum representing the possible results of adding an associated key to an account. + */ +export enum AddKeyFailure { + /** + * Success + */ + Ok = 0, + /** + * Unable to add new associated key because maximum amount of keys is reached + */ + MaxKeysLimit = 1, + /** + * Unable to add new associated key because given key already exists + */ + DuplicateKey = 2, + /** + * Unable to add new associated key due to insufficient permissions + */ + PermissionDenied = 3, +} + +/** + * Enum representing the possible results of updating an associated key of an account. + */ +export enum UpdateKeyFailure { + /** + * Success + */ + Ok = 0, + /** + * Key does not exist in the list of associated keys. + */ + MissingKey = 1, + /** + * Unable to update the associated key due to insufficient permissions + */ + PermissionDenied = 2, + /** + * Unable to update weight that would fall below any of action thresholds + */ + ThresholdViolation = 3, +} + +/** + * Enum representing the possible results of removing an associated key from an account. + */ +export enum RemoveKeyFailure { + /** + * Success + */ + Ok = 0, + /** + * Key does not exist in the list of associated keys. + */ + MissingKey = 1, + /** + * Unable to remove the associated key due to insufficient permissions + */ + PermissionDenied = 2, + /** + * Unable to remove a key which would violate action threshold constraints + */ + ThresholdViolation = 3, +} + +/** + * Enum representing the possible results of setting the threshold of an account. + */ +export enum SetThresholdFailure { + /** + * Success + */ + Ok = 0, + /** + * New threshold should be lower or equal than deployment threshold + */ + KeyManagementThreshold = 1, + /** + * New threshold should be lower or equal than key management threshold + */ + DeploymentThreshold = 2, + /** + * Unable to set action threshold due to insufficient permissions + */ + PermissionDeniedError = 3, + /** + * New threshold should be lower or equal than total weight of associated keys + */ + InsufficientTotalWeight = 4, +} + +/** + * Enum representing an action for which a threshold is being set. + */ +export enum ActionType { + /** + * Required by deploy execution. + */ + Deployment = 0, + /** + * Required when adding/removing associated keys, changing threshold levels. + */ + KeyManagement = 1, +} + +/** + * Adds an associated key to the account. Associated keys are the keys allowed to sign actions performed + * in the context of the account. + * + * @param AccountHash The public key to be added as the associated key. + * @param weight The weight that will be assigned to the new associated key. See [[setActionThreshold]] + * for more info about weights. + * @returns An instance of [[AddKeyFailure]] representing the result. + */ +export function addAssociatedKey(accountHash: AccountHash, weight: i32): AddKeyFailure { + const accountHashBytes = accountHash.toBytes(); + const ret = externals.add_associated_key(accountHashBytes.dataStart, accountHashBytes.length, weight); + return ret; +} + +/** + * Sets a threshold for the action performed in the context of the account. + * + * Each request has to be signed by one or more of the keys associated with the account. The action + * is only successful if the total weights of the signing associated keys is greater than the threshold. + * + * @param actionType The type of the action for which the threshold is being set. + * @param thresholdValue The minimum total weight of the keys of the action to be successful. + * @returns An instance of [[SetThresholdFailure]] representing the result. + */ +export function setActionThreshold(actionType: ActionType, thresholdValue: u8): SetThresholdFailure { + const ret = externals.set_action_threshold(actionType, thresholdValue); + return ret; +} + +/** + * Changes the weight of an existing associated key. See [[addAssociatedKey]] and [[setActionThreshold]] + * for info about associated keys and their weights. + * + * @param accountHash The associated key to be updated. + * @param weight The new desired weight of the associated key. + * @returns An instance of [[UpdateKeyFailure]] representing the result. + */ +export function updateAssociatedKey(accountHash: AccountHash, weight: i32): UpdateKeyFailure { + const accountHashBytes = accountHash.toBytes(); + const ret = externals.update_associated_key(accountHashBytes.dataStart, accountHashBytes.length, weight); + return ret; +} + +/** + * Removes the associated key from the account. See [[addAssociatedKey]] for more info about associated + * keys. + * + * @param accountHash The associated key to be removed. + * @returns An instance of [[RemoveKeyFailure]] representing the result. + */ +export function removeAssociatedKey(accountHash: AccountHash): RemoveKeyFailure { + const accountHashBytes = accountHash.toBytes(); + const ret = externals.remove_associated_key(accountHashBytes.dataStart, accountHashBytes.length); + return ret; +} + +/** + * Gets the [[URef]] representing the main purse of the account. + * + * @returns The [[URef]] that can be used to access the main purse. + */ +export function getMainPurse(): URef { + let data = new Uint8Array(UREF_SERIALIZED_LENGTH); + data.fill(0); + externals.get_main_purse(data.dataStart); + let urefResult = URef.fromBytes(data); + return urefResult.unwrap(); +} diff --git a/smart-contracts/contract-as/assembly/bignum.ts b/smart-contracts/contract-as/assembly/bignum.ts new file mode 100644 index 0000000000..add63d2a25 --- /dev/null +++ b/smart-contracts/contract-as/assembly/bignum.ts @@ -0,0 +1,597 @@ +import {Error, Result, Ref} from "./bytesrepr"; +import {Pair} from "./pair"; + +const HEX_LOWERCASE: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + +/** + * Fast lookup of ascii character into it's numerical value in base16 + */ +const HEX_DIGITS: i32[] = +[ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, + -1,0xa,0xb,0xc,0xd,0xe,0xf,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,0xa,0xb,0xc,0xd,0xe,0xf,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ]; + +/** + * An implementation of 512-bit unsigned integers. + */ +export class U512 { + private pn: Uint32Array; + + /** + * Constructs a new instance of U512. + */ + constructor() { + this.pn = new Uint32Array(16); // 512 bits total + } + + /** + * @returns The maximum possible value of a U512. + */ + static get MAX_VALUE(): U512 { + let value = new U512(); + value.pn.fill(0xffffffff); + return value; + } + + /** + * @returns The minimum possible value of a U512 (which is 0). + */ + static get MIN_VALUE(): U512 { + return new U512(); + } + + /** + * Constructs a new U512 from a string of hex digits. + */ + static fromHex(hex: String): U512 { + let res = new U512(); + res.setHex(hex); + return res; + } + + /** + * Converts a 64-bit unsigned integer into a U512. + * + * @param value The value to be converted. + */ + static fromU64(value: u64): U512 { + let res = new U512(); + res.setU64(value); + return res; + } + + /** + * Gets the width of the number in bytes. + */ + get width(): i32 { + return this.pn.length * 4; + } + + /** + * Sets the value of this U512 to a given 64-bit value. + * + * @param value The desired new value of this U512. + */ + setU64(value: u64): void { + this.pn.fill(0); + assert(this.pn.length >= 2); + this.pn[0] = (value & 0xffffffff); + this.pn[1] = (value >> 32); + } + + /** + * Sets the value of this U512 to a value represented by the given string of hex digits. + * + * @param value The string of hex digits representing the desired value. + */ + setHex(value: String): void { + if (value.length >= 2 && value[0] == '0' && (value[1] == 'x' || value[1] == 'X')) + value = value.substr(2); + + // Find the length + let digits = 0; + while (digits < value.length && HEX_DIGITS[value.charCodeAt(digits)] != -1 ) { + digits++; + } + + // Decodes hex string into an array of bytes + let bytes = new Uint8Array(this.width); + + // Convert ascii codes into values + let i = 0; + while (digits > 0 && i < bytes.length) { + bytes[i] = HEX_DIGITS[value.charCodeAt(--digits)]; + + if (digits > 0) { + bytes[i] |= HEX_DIGITS[value.charCodeAt(--digits)] << 4; + i++; + } + } + + // Reinterpret individual bytes back to u32 array + this.setBytesLE(bytes); + } + + /** + * Checks whether this U512 is equal to 0. + * + * @returns True if this U512 is 0, false otherwise. + */ + isZero(): bool { + for (let i = 0; i < this.pn.length; i++) { + if (this.pn[i] != 0) { + return false; + } + } + return true; + } + + /** + * The addition operator - adds two U512 numbers together. + */ + @operator("+") + add(other: U512): U512 { + assert(this.pn.length == other.pn.length); + // We do store carry as u64, to easily detect the overflow. + let carry = 0; + for (let i = 0; i < this.pn.length; i++) { + let n = carry + this.pn[i] + other.pn[i]; + // The actual value after (possibly) overflowing addition + this.pn[i] = (n & 0xffffffff); + // Anything above 2^32-1 is the overflow and its what we carry over + carry = (n >> 32); + } + return this; + } + + /** + * The negation operator - returns the two's complement of the argument. + */ + @operator.prefix("-") + neg(): U512 { + let ret = new U512(); + for (let i = 0; i < this.pn.length; i++) { + ret.pn[i] = ~this.pn[i]; + } + ++ret; + return ret; + } + + /** + * The subtraction operator - subtracts the two U512s. + */ + @operator("-") + sub(other: U512): U512 { + return this.add(-other); + } + + /** + * The multiplication operator - calculates the product of the two arguments. + */ + @operator("*") + mul(other: U512): U512 { + assert(this.pn.length == other.pn.length); + let ret = new U512(); + // A naive implementation of multiplication + for (let j = 0; j < this.pn.length; j++) { + let carry: u64 = 0; + for (let i = 0; i + j < this.pn.length; i++) { + // In a similar fashion to addition, we do arithmetic on 64 bit integers to detect overflow + let n: u64 = carry + ret.pn[i + j] + this.pn[j] * other.pn[i]; + ret.pn[i + j] = (n & 0xffffffff); + carry = (n >> 32); + } + } + return ret; + } + + /** + * Increments this U512. + */ + private increment(): void { + let i = 0; + while (i < this.pn.length && ++this.pn[i] == 0) { + i++; + } + } + + /** + * Decrements this U512. + */ + private decrement(): void { + let i = 0; + while (i < this.pn.length && --this.pn[i] == u32.MAX_VALUE) { + i++; + } + } + + /** + * Prefix operator `++` - increments this U512. + */ + @operator.prefix("++") + prefixInc(): U512 { + this.increment(); + return this; + } + + /** + * Prefix operator `--` - decrements this U512. + */ + @operator.prefix("--") + prefixDec(): U512 { + this.decrement(); + return this; + } + + /** + * Postfix operator `++` - increments this U512. + */ + @operator.postfix("++") + postfixInc(): U512 { + let cloned = this.clone(); + cloned.increment(); + return cloned; + } + + /** + * Postfix operator `--` - decrements this U512. + */ + @operator.postfix("--") + postfixDec(): U512 { + let cloned = this.clone(); + cloned.decrement(); + return cloned; + } + + /** + * Sets the values of the internally kept 32-bit "digits" (or "limbs") of the U512. + * + * @param pn The array of unsigned 32-bit integers to be used as the "digits"/"limbs". + */ + setValues(pn: Uint32Array): void { + for (let i = 0; i < this.pn.length; i++) { + this.pn[i] = pn[i]; + } + } + + /** + * Clones the U512. + */ + clone(): U512 { + let U512val = new U512(); + U512val.setValues(this.pn); + return U512val; + } + + /** + * Returns length of the integer in bits (not counting the leading zero bits). + */ + bits(): u32 { + for (let i = this.pn.length - 1; i >= 0; i--) { + if (this.pn[i] > 0) { + // Counts leading zeros + return 32 * i + (32 - clz(this.pn[i])); + } + } + return 0; + } + + /** + * Performs the integer division of `this/other`. + * + * @param other The divisor. + * @returns A pair consisting of the quotient and the remainder, or null if the divisor was 0. + */ + divMod(other: U512): Pair | null { + assert(this.pn.length == other.pn.length); + + let div = other.clone(); // make a copy, so we can shift. + let num = this.clone(); // make a copy, so we can subtract the quotient. + + let res = new U512(); + + let numBits = num.bits(); + let divBits = div.bits(); + + if (divBits == 0) { + // division by zero + return null; + } + + if (divBits > numBits) { + // the result is certainly 0 and rem is the lhs of equation. + let zero = new U512(); + return new Pair(zero, num); + } + + let shift: i32 = numBits - divBits; + div <<= shift; // shift so that div and num align. + + while (shift >= 0) { + if (num >= div) { + num -= div; + res.pn[shift / 32] |= (1 << (shift & 31)); // set a bit of the result. + } + div >>= 1; // shift back. + shift--; + } + // num now contains the remainder of the division. + return new Pair(res, num); + } + + /** + * The division operator - divides the arguments. + */ + @operator("/") + div(other: U512): U512 { + let divModResult = this.divMod(other); + assert(divModResult !== null); + return (>divModResult).first; + } + + /** + * The 'modulo' operator - calculates the remainder from the division of the arguments. + */ + @operator("%") + rem(other: U512): U512 { + let divModResult = this.divMod(other); + assert(divModResult !== null); + return (>divModResult).second; + } + + /** + * The bitwise left-shift operator. + */ + @operator("<<") + shl(shift: u32): U512 { + let res = new U512(); + + let k: u32 = shift / 32; + shift = shift % 32; + + for (let i = 0; i < this.pn.length; i++) { + if (i + k + 1 < this.pn.length && shift != 0) { + res.pn[i + k + 1] |= (this.pn[i] >> (32 - shift)); + } + if (i + k < this.pn.length) { + res.pn[i + k] |= (this.pn[i] << shift); + } + } + + return res; + } + + /** + * The bitwise right-shift operator. + */ + @operator(">>") + shr(shift: u32): U512 { + let res = new U512(); + + let k = shift / 32; + shift = shift % 32; + + for (let i = 0; i < this.pn.length; i++) { + if (i - k - 1 >= 0 && shift != 0) { + res.pn[i - k - 1] |= (this.pn[i] << (32 - shift)); + } + if (i - k >= 0) { + res.pn[i - k] |= (this.pn[i] >> shift); + } + } + + return res; + } + + /** + * Compares `this` and `other`. + * + * @param other The number to compare `this` to. + * @returns -1 if `this` is less than `other`, 1 if `this` is greater than `other`, 0 if `this` + * and `other` are equal. + */ + cmp(other: U512): i32 { + assert(this.pn.length == other.pn.length); + for (let i = this.pn.length - 1; i >= 0; --i) { + if (this.pn[i] < other.pn[i]) { + return -1; + } + if (this.pn[i] > other.pn[i]) { + return 1; + } + } + return 0; + } + + /** + * The equality operator. + * + * @returns True if `this` and `other` are equal, false otherwise. + */ + @operator("==") + eq(other: U512): bool { + return this.cmp(other) == 0; + } + + /** + * The not-equal operator. + * + * @returns False if `this` and `other` are equal, true otherwise. + */ + @operator("!=") + neq(other: U512): bool { + return this.cmp(other) != 0; + } + + /** + * The greater-than operator. + * + * @returns True if `this` is greater than `other`, false otherwise. + */ + @operator(">") + gt(other: U512): bool { + return this.cmp(other) == 1; + } + + /** + * The less-than operator. + * + * @returns True if `this` is less than `other`, false otherwise. + */ + @operator("<") + lt(other: U512): bool { + return this.cmp(other) == -1; + } + + /** + * The greater-than-or-equal operator. + * + * @returns True if `this` is greater than or equal to `other`, false otherwise. + */ + @operator(">=") + gte(other: U512): bool { + return this.cmp(other) >= 0; + } + + /** + * The less-than-or-equal operator. + * + * @returns True if `this` is less than or equal to `other`, false otherwise. + */ + @operator("<=") + lte(other: U512): bool { + return this.cmp(other) <= 0; + } + + /** + * Returns a little-endian byte-array representation of this U512 (i.e. `result[0]` is the least + * significant byte. + */ + toBytesLE(): Uint8Array { + let bytes = new Uint8Array(this.width); + // Copy array of u32 into array of u8 + for (let i = 0; i < this.pn.length; i++) { + store(bytes.dataStart + (i * 4), this.pn[i]); + } + return bytes; + } + + /** + * Sets the value of this U512 to the value represented by `bytes` when treated as a + * little-endian representation of a number. + */ + setBytesLE(bytes: Uint8Array): void { + for (let i = 0; i < this.pn.length; i++) { + let num = load(bytes.dataStart + (i * 4)); + this.pn[i] = num; + } + } + + /** + * Returns a string of hex digits representing the value of this U512. + */ + private toHex(): String { + let bytes = this.toBytesLE(); + let result = ""; + + // Skips zeros in the back to make the numbers readable without tons of zeros in front + let backZeros = bytes.length - 1; + + while (backZeros >= 0 && bytes[backZeros--] == 0) {} + + // First digit could be still 0 so skip it + let firstByte = bytes[++backZeros]; + if ((firstByte & 0xF0) == 0) { + // Skips the hi byte if the first character of the output base16 would be `0` + // This way the hex string wouldn't be something like "01" + result += HEX_LOWERCASE[firstByte & 0x0F]; + } + else { + result += HEX_LOWERCASE[firstByte >> 4]; + result += HEX_LOWERCASE[firstByte & 0x0F]; + } + + // Convert the rest of bytes into base16 + for (let i = backZeros - 1; i >= 0; i--) { + let value = bytes[i]; + result += HEX_LOWERCASE[value >> 4]; + result += HEX_LOWERCASE[value & 0x0F]; + } + return result; + } + + /** + * An alias for [[toHex]]. + */ + toString(): String { + return this.toHex(); + } + + /** + * Deserializes a U512 from an array of bytes. The array should represent a correct U512 in the + * CasperLabs serialization format. + * + * @returns A [[Result]] that contains the deserialized U512 if the deserialization was + * successful, or an error otherwise. + */ + static fromBytes(bytes: Uint8Array): Result { + if (bytes.length < 1) { + return new Result(null, Error.EarlyEndOfStream, 0); + } + + const lengthPrefix = bytes[0]; + if (lengthPrefix > bytes.length) { + return new Result(null, Error.EarlyEndOfStream, 0); + } + + + let res = new U512(); + + // Creates a buffer so individual bytes can be placed there + let buffer = new Uint8Array(res.width); + for (let i = 0; i < lengthPrefix; i++) { + buffer[i] = bytes[i + 1]; + } + + res.setBytesLE(buffer); + let ref = new Ref(res); + return new Result(ref, Error.Ok, 1 + lengthPrefix); + } + + /** + * Serializes the U512 into an array of bytes that represents it in the CasperLabs serialization + * format. + */ + toBytes(): Array { + let bytes = this.toBytesLE(); + let skipZeros = bytes.length - 1; + + // Skip zeros at the end + while (skipZeros >= 0 && bytes[skipZeros] == 0) { + skipZeros--; + } + + // Continue + let lengthPrefix = skipZeros + 1; + + let result = new Array(1 + lengthPrefix); + result[0] = lengthPrefix; + for (let i = 0; i < lengthPrefix; i++) { + result[1 + i] = bytes[i]; + } + return result; + } +}; diff --git a/smart-contracts/contract-as/assembly/bytesrepr.ts b/smart-contracts/contract-as/assembly/bytesrepr.ts new file mode 100644 index 0000000000..002b908d94 --- /dev/null +++ b/smart-contracts/contract-as/assembly/bytesrepr.ts @@ -0,0 +1,399 @@ +import { Pair } from "./pair"; +import { typedToArray, encodeUTF8 } from "./utils"; +import { ErrorCode, Error as StdError } from "./error"; + +/** + * Boxes a value which could then be nullable in any context. + */ +export class Ref { + constructor(public value: T) {} +} + + +/** + * Enum representing possible results of deserialization. + */ +export enum Error { + /** + * Last operation was a success + */ + Ok = 0, + /** + * Early end of stream + */ + EarlyEndOfStream = 1, + /** + * Unexpected data encountered while decoding byte stream + */ + FormattingError = 2, +} + +/** + * Converts bytesrepr's [[Error]] into a standard [[ErrorCode]]. + * @internal + * @returns An instance of [[Ref]] object for non-zero error code, otherwise a null. + */ +function toErrorCode(error: Error): Ref | null { + switch (error) { + case Error.EarlyEndOfStream: + return new Ref(ErrorCode.EarlyEndOfStream); + case Error.FormattingError: + return new Ref(ErrorCode.Formatting); + default: + return null; + } +} + + +/** + * Class representing a result of an operation that might have failed. Can contain either a value + * resulting from a successful completion of a calculation, or an error. Similar to `Result` in Rust + * or `Either` in Haskell. + */ +export class Result { + /** + * Creates new Result with wrapped value + * @param value Ref-wrapped value (success) or null (error) + * @param error Error value + * @param position Position of input stream + */ + constructor(public ref: Ref | null, public error: Error, public position: u32) {} + + /** + * Assumes that reference wrapper contains a value and then returns it + */ + get value(): T { + assert(this.hasValue()); + let ref = >this.ref; + return ref.value; + } + + /** + * Checks if given Result contains a value + */ + hasValue(): bool { + return this.ref !== null; + } + + /** + * Checks if error value is set. + * + * Truth also implies !hasValue(), false value implies hasValue() + */ + hasError(): bool { + return this.error != Error.Ok; + } + + /** + * For nullable types, this returns the value itself, or a null. + */ + ok(): T | null { + return this.hasValue() ? this.value : null; + } + + /** + * Returns success value, or reverts error value. + */ + unwrap(): T { + const errorCode = toErrorCode(this.error); + if (errorCode != null) { + const error = new StdError(errorCode.value); + error.revert(); + return unreachable(); + } + return this.value; + } +} + +/** + * Serializes an `u8` as an array of bytes. + * + * @returns An array containing a single byte: `num`. + */ +export function toBytesU8(num: u8): u8[] { + return [num]; +} + +/** + * Deserializes a [[T]] from an array of bytes. + * + * @returns A [[Result]] that contains the value of type `T`, or an error if deserialization failed. + */ +export function fromBytesLoad(bytes: Uint8Array): Result { + let expectedSize = changetype(sizeof()) + if (bytes.length < expectedSize) { + return new Result(null, Error.EarlyEndOfStream, 0); + } + const value = load(bytes.dataStart); + return new Result(new Ref(value), Error.Ok, expectedSize); +} + +/** + * Deserializes a `u8` from an array of bytes. + */ +export function fromBytesU8(bytes: Uint8Array): Result { + return fromBytesLoad(bytes); +} + +/** + * Converts `u32` to little endian. + */ +export function toBytesU32(num: u32): u8[] { + let bytes = new Uint8Array(4); + store(bytes.dataStart, num); + let result = new Array(4); + for (var i = 0; i < 4; i++) { + result[i] = bytes[i]; + } + return result; +} + +/** + * Deserializes a `u32` from an array of bytes. + */ +export function fromBytesU32(bytes: Uint8Array): Result { + return fromBytesLoad(bytes); +} + +/** + * Converts `i32` to little endian. + */ +export function toBytesI32(num: i32): u8[] { + let bytes = new Uint8Array(4); + store(bytes.dataStart, num); + let result = new Array(4); + for (var i = 0; i < 4; i++) { + result[i] = bytes[i]; + } + return result; +} + +/** + * Deserializes an `i32` from an array of bytes. + */ +export function fromBytesI32(bytes: Uint8Array): Result { + return fromBytesLoad(bytes); +} + +/** + * Converts `u64` to little endian. + */ +export function toBytesU64(num: u64): u8[] { + let bytes = new Uint8Array(8); + store(bytes.dataStart, num); + let result = new Array(8); + for (var i = 0; i < 8; i++) { + result[i] = bytes[i]; + } + return result; +} + +/** + * Deserializes a `u64` from an array of bytes. + */ +export function fromBytesU64(bytes: Uint8Array): Result { + return fromBytesLoad(bytes); +} + +/** + * Joins a pair of byte arrays into a single array. + */ +export function toBytesPair(key: u8[], value: u8[]): u8[] { + return key.concat(value); +} + +/** + * Serializes a map into an array of bytes. + * + * @param map A map container. + * @param serializeKey A function that will serialize given key. + * @param serializeValue A function that will serialize given value. + */ +export function toBytesMap(vecOfPairs: Array>, serializeKey: (key: K) => Array, serializeValue: (value: V) => Array): Array { + const len = vecOfPairs.length; + var bytes = toBytesU32(len); + for (var i = 0; i < len; i++) { + bytes = bytes.concat(serializeKey(vecOfPairs[i].first)); + bytes = bytes.concat(serializeValue(vecOfPairs[i].second)); + } + return bytes; +} + +/** + * Deserializes an array of bytes into a map. + * + * @param bytes The array of bytes to be deserialized. + * @param decodeKey A function deserializing the key type. + * @param decodeValue A function deserializing the value type. + * @returns An array of key-value pairs or an error in case of failure. + */ +export function fromBytesMap( + bytes: Uint8Array, + decodeKey: (bytes1: Uint8Array) => Result, + decodeValue: (bytes2: Uint8Array) => Result, +): Result>> { + const lengthResult = fromBytesU32(bytes); + if (lengthResult.error != Error.Ok) { + return new Result>>(null, Error.EarlyEndOfStream, 0); + } + const length = lengthResult.value; + + // Tracks how many bytes are parsed + let currentPos = lengthResult.position; + + let result = new Array>(); + + if (length == 0) { + let ref = new Ref>>(result); + return new Result>>(ref, Error.Ok, lengthResult.position); + } + + let bytes = bytes.subarray(currentPos); + + for (let i = 0; i < changetype(length); i++) { + const keyResult = decodeKey(bytes); + if (keyResult.error != Error.Ok) { + return new Result>>(null, keyResult.error, keyResult.position); + } + + currentPos += keyResult.position; + bytes = bytes.subarray(keyResult.position); + + let valueResult = decodeValue(bytes); + if (valueResult.error != Error.Ok) { + return new Result>>(null, valueResult.error, valueResult.position); + } + + currentPos += valueResult.position; + bytes = bytes.subarray(valueResult.position); + + let pair = new Pair(keyResult.value, valueResult.value); + result.push(pair); + } + + let ref = new Ref>>(result); + return new Result>>(ref, Error.Ok, currentPos); +} + +/** + * Serializes a string into an array of bytes. + */ +export function toBytesString(s: String): u8[] { + let bytes = toBytesU32(s.length); + return bytes.concat(typedToArray(encodeUTF8(s))); +} + +/** + * Deserializes a string from an array of bytes. + */ +export function fromBytesString(s: Uint8Array): Result { + var lenResult = fromBytesI32(s); + if (lenResult.error != Error.Ok) { + return new Result(null, Error.EarlyEndOfStream, 0); + } + + let currentPos = lenResult.position; + + const leni32 = lenResult.value; + if (s.length < leni32 + 4) { + return new Result(null, Error.EarlyEndOfStream, 0); + } + var result = ""; + for (var i = 0; i < leni32; i++) { + result += String.fromCharCode(s[4 + i]); + } + let ref = new Ref(result); + return new Result(ref, Error.Ok, currentPos + leni32); +} + +/** + * Serializes an array of bytes. + */ +export function toBytesArrayU8(arr: Array): u8[] { + let bytes = toBytesU32(arr.length); + return bytes.concat(arr); +} + +/** + * Deserializes an array of bytes. + */ +export function fromBytesArrayU8(bytes: Uint8Array): Result> { + var lenResult = fromBytesI32(bytes); + if (lenResult.error != Error.Ok) { + return new Result(null, Error.EarlyEndOfStream, 0); + } + + let currentPos = lenResult.position; + + const leni32 = lenResult.value; + if (s.length < leni32 + 4) { + return new Result(null, Error.EarlyEndOfStream, 0); + } + + let result = typedToArray(bytes.subarray(currentPos)); + let ref = new Ref(result); + return new Result(ref, Error.Ok, currentPos + leni32, currentPos + leni32); +} + +/** + * Serializes a vector of values of type `T` into an array of bytes. + */ +export function toBytesVecT(ts: Array, encodeItem: (item: T) => Array): Array { + var bytes = toBytesU32(ts.length); + for (let i = 0; i < ts.length; i++) { + var itemBytes = encodeItem(ts[i]); + bytes = bytes.concat(itemBytes); + } + return bytes; +} + +/** + * Deserializes an array of bytes into an array of type `T`. + * + * @param bytes The array of bytes to be deserialized. + * @param decodeItem A function deserializing a value of type `T`. + */ +export function fromBytesArray(bytes: Uint8Array, decodeItem: (bytes: Uint8Array) => Result): Result> { + var lenResult = fromBytesI32(bytes); + if (lenResult.error != Error.Ok) { + return new Result>(null, Error.EarlyEndOfStream, 0); + } + + let len = lenResult.value; + let currentPos = lenResult.position; + let head = bytes.subarray(currentPos); + + let result: Array = new Array(); + + for (let i = 0; i < len; ++i) { + let decodeResult = decodeItem(head); + if (decodeResult.error != Error.Ok) { + return new Result>(null, decodeResult.error, 0); + } + currentPos += decodeResult.position; + result.push(decodeResult.value); + head = head.subarray(decodeResult.position); + } + + let ref = new Ref>(result); + return new Result>(ref, Error.Ok, currentPos); +} + +/** + * Deserializes a list of strings from an array of bytes. + */ +export function fromBytesStringList(bytes: Uint8Array): Result> { + return fromBytesArray(bytes, fromBytesString); +} + +/** + * Serializes a list of strings into an array of bytes. + */ +export function toBytesStringList(arr: String[]): u8[] { + let data = toBytesU32(arr.length); + for (let i = 0; i < arr.length; i++) { + const strBytes = toBytesString(arr[i]); + data = data.concat(strBytes); + } + return data; +} diff --git a/smart-contracts/contract-as/assembly/clvalue.ts b/smart-contracts/contract-as/assembly/clvalue.ts new file mode 100644 index 0000000000..a227692b16 --- /dev/null +++ b/smart-contracts/contract-as/assembly/clvalue.ts @@ -0,0 +1,177 @@ +import {toBytesArrayU8, toBytesString, toBytesI32, toBytesU32, toBytesStringList, toBytesU64} from "./bytesrepr"; +import {U512} from "./bignum"; +import {URef} from "./uref"; +import {Key} from "./key"; +import {Option} from "./option"; + +/** + * CasperLabs types, i.e. types which can be stored and manipulated by smart contracts. + * + * Provides a description of the underlying data type of a [[CLValue]]. + */ +export enum CLTypeTag { + /** A boolean value */ + Bool = 0, + /** A 32-bit signed integer */ + I32 = 1, + /** A 64-bit signed integer */ + I64 = 2, + /** An 8-bit unsigned integer (a byte) */ + U8 = 3, + /** A 32-bit unsigned integer */ + U32 = 4, + /** A 64-bit unsigned integer */ + U64 = 5, + /** A 128-bit unsigned integer */ + U128 = 6, + /** A 256-bit unsigned integer */ + U256 = 7, + /** A 512-bit unsigned integer */ + U512 = 8, + /** A unit type, i.e. type with no values (analogous to `void` in C and `()` in Rust) */ + Unit = 9, + /** A string of characters */ + String = 10, + /** A key in the global state - URef/hash/etc. */ + Key = 11, + /** An Unforgeable Reference (URef) */ + Uref = 12, + /** An [[Option]], i.e. a type that can contain a value or nothing at all */ + Option = 13, + /** A list of values */ + List = 14, + /** A fixed-length list of values */ + Fixed_list = 15, + /** + * A [[Result]], i.e. a type that can contain either a value representing success or one representing failure. + */ + Result = 16, + /** A key-value map. */ + Map = 17, + /** A 1-value tuple. */ + Tuple1 = 18, + /** A 2-value tuple, i.e. a pair of values. */ + Tuple2 = 19, + /** A 3-value tuple. */ + Tuple3 = 20, + /** A value of any type. */ + Any = 21, +} + +export class CLType { + tag: CLTypeTag; + bytes: Array; + + constructor(tag: CLTypeTag, extra: Array | null = null) { + this.tag = tag; + this.bytes = [tag]; + if (extra !== null) { + this.bytes = this.bytes.concat(>extra); + } + } + + static fixedList(typeTag: CLType, size: u32): CLType { + let extra = typeTag.bytes; + extra = extra.concat(toBytesU32(size)); + + let clType = new CLType(CLTypeTag.Fixed_list, extra); + + return clType; + } + + static list(typeTag: CLType): CLType { + return new CLType(CLTypeTag.List, typeTag.bytes); + } + + static option(typeTag: CLType): CLType { + return new CLType(CLTypeTag.Option, typeTag.bytes); + } + + toBytes(): u8[] { + return this.bytes; + } +}; + +/** + * A CasperLabs value, i.e. a value which can be stored and manipulated by smart contracts. + * + * It holds the underlying data as a type-erased, serialized array of bytes and also holds the + * [[CLType]] of the underlying data as a separate member. + */ +export class CLValue { + bytes: u8[]; + clType: CLType; + + /** + * Constructs a new `CLValue` with given underlying data and type. + */ + constructor(bytes: u8[], clType: CLType) { + this.bytes = bytes; + this.clType = clType; + } + + /** + * Creates a `CLValue` holding a string. + */ + static fromString(s: String): CLValue { + return new CLValue(toBytesString(s), new CLType(CLTypeTag.String)); + } + + /** + * Creates a `CLValue` holding an unsigned 512-bit integer. + */ + static fromU512(value: U512): CLValue { + return new CLValue(value.toBytes(), new CLType(CLTypeTag.U512)); + } + + /** + * Creates a `CLValue` holding a signed 32-bit integer. + */ + static fromI32(value: i32): CLValue { + return new CLValue(toBytesI32(value), new CLType(CLTypeTag.I32)); + } + + /** + * Creates a `CLValue` holding an unsigned 64-bit integer. + */ + static fromU64(value: u64): CLValue { + return new CLValue(toBytesU64(value), new CLType(CLTypeTag.U64)); + } + + /** + * Creates a `CLValue` holding a [[Key]]. + */ + static fromKey(key: Key): CLValue{ + return new CLValue(key.toBytes(), new CLType(CLTypeTag.Key)); + } + + /** + * Creates a `CLValue` holding a [[URef]]. + */ + static fromURef(uref: URef): CLValue { + return new CLValue(uref.toBytes(), new CLType(CLTypeTag.Uref)); + } + + /** + * Creates a `CLValue` holding a list of strings. + */ + static fromStringList(values: String[]): CLValue { + return new CLValue(toBytesStringList(values), CLType.list(new CLType(CLTypeTag.String))); + } + + /** + * Creates a `CLValue` holding an [[Option]]. + */ + static fromOption(value: Option, nestedT: CLType): CLValue { + return new CLValue(value.toBytes(), CLType.option(nestedT)); + } + + /** + * Serializes a `CLValue` into an array of bytes. + */ + toBytes(): u8[] { + let data = toBytesArrayU8(this.bytes); + data = data.concat(this.clType.bytes); + return data; + } +} diff --git a/smart-contracts/contract-as/assembly/constants.ts b/smart-contracts/contract-as/assembly/constants.ts new file mode 100644 index 0000000000..34fb309bab --- /dev/null +++ b/smart-contracts/contract-as/assembly/constants.ts @@ -0,0 +1,34 @@ +/** + * Length of [[URef]] address field. + * @internal + */ +export const UREF_ADDR_LENGTH = 32; + +/** + * Length of hash variant of a [[Key]]. + * @internal + */ +export const KEY_HASH_LENGTH = 32; + +/** + * Serialized length of [[AccessRights]] field. + * @internal + */ +export const ACCESS_RIGHTS_SERIALIZED_LENGTH = 1; + +/** + * Serialized length of [[URef]] object. + * @internal + */ +export const UREF_SERIALIZED_LENGTH = UREF_ADDR_LENGTH + ACCESS_RIGHTS_SERIALIZED_LENGTH; + +/** + * Serialized length of ID of key. + * @internal + */ +export const KEY_ID_SERIALIZED_LENGTH: i32 = 1; // u8 used to determine the ID + +/** + * Serialized length of [[Key]] object. + */ +export const KEY_UREF_SERIALIZED_LENGTH = KEY_ID_SERIALIZED_LENGTH + UREF_SERIALIZED_LENGTH; \ No newline at end of file diff --git a/smart-contracts/contract-as/assembly/contracts.ts b/smart-contracts/contract-as/assembly/contracts.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/smart-contracts/contract-as/assembly/error.ts b/smart-contracts/contract-as/assembly/error.ts new file mode 100644 index 0000000000..f3ccd5585d --- /dev/null +++ b/smart-contracts/contract-as/assembly/error.ts @@ -0,0 +1,193 @@ +import * as externals from "./externals"; + +/** + * Offset of a reserved range dedicated for system contract errors. + * @internal + */ +const SYSTEM_CONTRACT_ERROR_CODE_OFFSET: u32 = 65024; + +/** + * Offset of user errors + */ +const USER_ERROR_CODE_OFFSET: u32 = 65535; + +/** + * Standard error codes which can be encountered while running a smart contract. + * + * An [[ErrorCode]] can be passed to [[Error.fromErrorCode]] function to create an error object. + * This error object later can be used to stop execution by using [[Error.revert]] method. + */ +export const enum ErrorCode { + /** Optional data was unexpectedly `None`. */ + None = 1, + /** Specified argument not provided. */ + MissingArgument = 2, + /** Argument not of correct type. */ + InvalidArgument = 3, + /** Failed to deserialize a value. */ + Deserialize = 4, + /** `casperlabs_contract::storage::read()` returned an error. */ + Read = 5, + /** The given key returned a `None` value. */ + ValueNotFound = 6, + /** Failed to find a specified contract. */ + ContractNotFound = 7, + /** A call to [[getKey]] returned a failure. */ + GetKey = 8, + /** The [[Key]] variant was not as expected. */ + UnexpectedKeyVariant = 9, + /** The `Contract` variant was not as expected. */ + UnexpectedContractRefVariant = 10, + /** Invalid purse name given. */ + InvalidPurseName = 11, + /** Invalid purse retrieved. */ + InvalidPurse = 12, + /** Failed to upgrade contract at [[URef]]. */ + UpgradeContractAtURef = 13, + /** Failed to transfer motes. */ + Transfer = 14, + /** The given [[URef]] has no access rights. */ + NoAccessRights = 15, + /** A given type could not be constructed from a [[CLValue]]. */ + CLTypeMismatch = 16, + /** Early end of stream while deserializing. */ + EarlyEndOfStream = 17, + /** Formatting error while deserializing. */ + Formatting = 18, + /** Not all input bytes were consumed in deserializing operation */ + LeftOverBytes = 19, + /** Out of memory error. */ + OutOfMemory = 20, + /** There are already maximum public keys associated with the given account. */ + MaxKeysLimit = 21, + /** The given public key is already associated with the given account. */ + DuplicateKey = 22, + /** Caller doesn't have sufficient permissions to perform the given action. */ + PermissionDenied = 23, + /** The given public key is not associated with the given account. */ + MissingKey = 24, + /** Removing/updating the given associated public key would cause the total weight of all remaining `AccountHash`s to fall below one of the action thresholds for the given account. */ + ThresholdViolation = 25, + /** Setting the key-management threshold to a value lower than the deployment threshold is disallowed. */ + KeyManagementThreshold = 26, + /** Setting the deployment threshold to a value greater than any other threshold is disallowed. */ + DeploymentThreshold = 27, + /** Setting a threshold to a value greater than the total weight of associated keys is disallowed. */ + InsufficientTotalWeight = 28, + /** The given `u32` doesn't map to a [[SystemContractType]]. */ + InvalidSystemContract = 29, + /** Failed to create a new purse. */ + PurseNotCreated = 30, + /** An unhandled value, likely representing a bug in the code. */ + Unhandled = 31, + /** The provided buffer is too small to complete an operation. */ + BufferTooSmall = 32, + /** No data available in the host buffer. */ + HostBufferEmpty = 33, + /** The host buffer has been set to a value and should be consumed first by a read operation. */ + HostBufferFull = 34, +} + + +/** + * This class represents error condition and is constructed by passing an error value. + * + * The variants are split into numeric ranges as follows: + * + * | Inclusive range | Variant(s) | + * | ----------------| ---------------------------------------------| + * | [1, 65023] | all except `Mint`, `ProofOfStake` and `User`. Can be created with [[Error.fromErrorCode]] | + * | [65024, 65279] | `Mint` - instantiation currently unsupported | + * | [65280, 65535] | `ProofOfStake` errors | + * | [65536, 131071] | User error codes created with [[Error.fromUserError]] | + * + * ## Example usage + * + * ```typescript + * // Creating using user error which adds 65536 to the error value. + * Error.fromUserError(1234).revert(); + * + * // Creating using standard error variant. + * Error.fromErrorCode(ErrorCode.InvalidArguent).revert(); + * ``` + */ +export class Error { + private errorCodeValue: u32; + + /** + * Creates an error object with given error value. + * + * Recommended way to use this class is through its static members: + * + * * [[Error.fromUserCode]] + * * [[Error.fromErrorCode]] + * @param value Error value + */ + constructor(value: u32) { + this.errorCodeValue = value; + } + + /** + * Creates an error object from a result value. + * + * Results in host interface contains 0 for a successful operation, + * or a non-zero standardized error otherwise. + * + * @param result A result value obtained from host interface functions. + * @returns Error object with an error [[ErrorCode]] variant. + */ + static fromResult(result: u32): Error | null { + if (result == 0) { + // Ok + return null; + } + return new Error(result); + } + + /** + * Creates new error from user value. + * + * Actual value held by returned [[Error]] object will be 65536 with added passed value. + * @param userErrorCodeValue + */ + static fromUserError(userErrorCodeValue: u16): Error { + return new Error(USER_ERROR_CODE_OFFSET + 1 + userErrorCodeValue); + } + + /** + * Creates new error object from an [[ErrorCode]] value. + * + * @param errorCode Variant of a standarized error. + */ + static fromErrorCode(errorCode: ErrorCode): Error { + return new Error(errorCode); + } + + /** + * Returns an error value. + */ + value(): u32{ + return this.errorCodeValue; + } + + /** + * Checks if error value is contained within user error range. + */ + isUserError(): bool{ + return this.errorCodeValue > USER_ERROR_CODE_OFFSET; + } + + /** + * Checks if error value is contained within system contract error range. + */ + isSystemContractError(): bool{ + return this.errorCodeValue >= SYSTEM_CONTRACT_ERROR_CODE_OFFSET && this.errorCodeValue <= USER_ERROR_CODE_OFFSET; + } + + /** + * Reverts execution of current contract with an error value contained within this error instance. + */ + revert(): void { + externals.revert(this.errorCodeValue); + } +} diff --git a/smart-contracts/contract-as/assembly/externals.ts b/smart-contracts/contract-as/assembly/externals.ts new file mode 100644 index 0000000000..5990e2a04b --- /dev/null +++ b/smart-contracts/contract-as/assembly/externals.ts @@ -0,0 +1,207 @@ +/** @hidden */ +@external("env", "read_value") +export declare function read_value(key_ptr: usize, key_size: usize, value_size: usize): i32; +/** @hidden */ +@external("env", "read_value_local") +export declare function read_value_local(key_ptr: usize, key_size: usize, output_size: usize): i32; +/** @hidden */ +@external("env", "write") +export declare function write(key_ptr: usize, key_size: usize, value_ptr: usize, value_size: usize): void; +/** @hidden */ +@external("env", "write_local") +export declare function write_local(key_ptr: usize, key_size: usize, value_ptr: usize, value_size: usize): void; +/** @hidden */ +@external("env", "add") +export declare function add(key_ptr: usize, key_size: usize, value_ptr: usize, value_size: usize): void; +/** @hidden */ +@external("env", "add_local") +export declare function add_local(key_ptr: usize, key_size: usize, value_ptr: usize, value_size: usize): void; +/** @hidden */ +@external("env", "new_uref") +export declare function new_uref(uref_ptr: usize, value_ptr: usize, value_size: usize): void; +@external("env", "load_named_keys") +export declare function load_named_keys(total_keys: usize, result_size: usize): i32; +/** @hidden */ +@external("env", "get_named_arg") +export declare function get_named_arg(name_ptr: usize, name_size: usize, dest_ptr: usize, dest_size: usize): i32; +/** @hidden */ +@external("env", "get_named_arg_size") +export declare function get_named_arg_size(name_ptr: usize, name_size: usize, dest_size: usize): i32; +/** @hidden */ +@external("env", "ret") +export declare function ret(value_ptr: usize, value_size: usize): void; +/** @hidden */ +@external("env", "call_contract") +export declare function call_contract(contract_hash_ptr: usize, contract_hash_size: usize, entry_point_name_ptr: usize, entry_point_name_size: usize, runtime_args_ptr: usize, runtime_args_size: usize, result_size: usize): i32; +/** @hidden */ +@external("env", "call_versioned_contract") +export declare function call_versioned_contract( + contract_package_hash_ptr: usize, + contract_package_hash_size: usize, + contract_version_ptr: usize, + contract_version_size: usize, + entry_point_name_ptr: usize, + entry_point_name_size: usize, + runtime_args_ptr: usize, + runtime_args_size: usize, + result_size: usize, +): i32; +/** @hidden */ +@external("env", "get_key") +export declare function get_key( + name_ptr: usize, + name_size: usize, + output_ptr: usize, + output_size: usize, + bytes_written_ptr: usize, +): i32; +/** @hidden */ +@external("env", "has_key") +export declare function has_key(name_ptr: usize, name_size: usize): i32; +/** @hidden */ +@external("env", "put_key") +export declare function put_key(name_ptr: usize, name_size: usize, key_ptr: usize, key_size: usize): void; +/** @hidden */ +@external("env", "remove_key") +export declare function remove_key(name_ptr: usize, name_size: u32): void; +/** @hidden */ +@external("env", "revert") +export declare function revert(err_code: i32): void; +/** @hidden */ +@external("env", "is_valid_uref") +export declare function is_valid_uref(target_ptr: usize, target_size: u32): i32; +/** @hidden */ +@external("env", "add_associated_key") +export declare function add_associated_key(account_hash_ptr: usize, account_hash_size: usize, weight: i32): i32; +/** @hidden */ +@external("env", "remove_associated_key") +export declare function remove_associated_key(account_hash_ptr: usize, account_hash_size: usize): i32; +/** @hidden */ +@external("env", "update_associated_key") +export declare function update_associated_key(account_hash_ptr: usize, account_hash_size: usize, weight: i32): i32; +/** @hidden */ +@external("env", "set_action_threshold") +export declare function set_action_threshold(permission_level: u32, threshold: i32): i32; +/** @hidden */ +@external("env", "get_blocktime") +export declare function get_blocktime(dest_ptr: usize): void; +/** @hidden */ +@external("env", "get_caller") +export declare function get_caller(output_size: usize): i32; +/** @hidden */ +@external("env", "create_purse") +export declare function create_purse(purse_ptr: usize, purse_size: u32): i32; +/** @hidden */ +@external("env", "transfer_to_account") +export declare function transfer_to_account( + target_ptr: usize, + target_size: u32, + amount_ptr: usize, + amount_size: u32, +): i32; +/** @hidden */ +@external("env", "transfer_from_purse_to_account") +export declare function transfer_from_purse_to_account( + source_ptr: usize, + source_size: u32, + target_ptr: usize, + target_size: u32, + amount_ptr: usize, + amount_size: u32, +): i32; +/** @hidden */ +@external("env", "transfer_from_purse_to_purse") +export declare function transfer_from_purse_to_purse( + source_ptr: usize, + source_size: u32, + target_ptr: usize, + target_size: u32, + amount_ptr: usize, + amount_size: u32, +): i32; +/** @hidden */ +@external("env", "get_balance") +export declare function get_balance(purse_ptr: usize, purse_size: usize, result_size: usize): i32; +/** @hidden */ +@external("env", "get_phase") +export declare function get_phase(dest_ptr: usize): void; +/** @hidden */ +@external("env", "upgrade_contract_at_uref") +export declare function upgrade_contract_at_uref( + name_ptr: usize, + name_size: u32, + key_ptr: usize, + key_size: u32 +): i32; +/** @hidden */ +@external("env", "get_system_contract") +export declare function get_system_contract(system_contract_index: u32, dest_ptr: usize, dest_size: u32): i32; +/** @hidden */ +@external("env", "get_main_purse") +export declare function get_main_purse(dest_ptr: usize): void; +/** @hidden */ +@external("env", "read_host_buffer") +export declare function read_host_buffer(dest_ptr: usize, dest_size: u32, bytes_written: usize): i32; +/** @hidden */ +@external("env", "remove_contract_user_group") +export declare function remove_contract_user_group( + contract_package_hash_ptr: usize, + contract_package_hash_size: usize, + label_ptr: usize, + label_size: usize): i32; +/** @hidden */ +@external("env", "provision_contract_user_group_uref") +export declare function provision_contract_user_group_uref( + contract_package_hash_ptr: usize, + contract_package_hash_size: usize, + label_ptr: usize, + label_size: usize, + value_size_ptr: usize, +): i32; +/** @hidden */ +@external("env", "remove_contract_user_group_urefs") +export declare function remove_contract_user_group_urefs( + contract_package_hash_ptr: usize, + contract_package_hash_size: usize, + label_ptr: usize, + label_size: usize, + urefs_ptr: usize, + urefs_size: usize, +): i32; +/** @hidden */ +@external("env", "create_contract_package_at_hash") +export declare function create_contract_package_at_hash(hash_addr_ptr: usize, access_addr_ptr: usize): void; +/** @hidden */ +@external("env", "add_contract_version") +export declare function add_contract_version( + contract_package_hash_ptr: usize, + contract_package_hash_size: usize, + version_ptr: usize, + entry_points_ptr: usize, + entry_points_size: usize, + named_keys_ptr: usize, + named_keys_size: usize, + output_ptr: usize, + output_size: usize, + bytes_written_ptr: usize, +): i32; +/** @hidden */ +@external("env", "create_contract_user_group") +export declare function create_contract_user_group( + contract_package_hash_ptr: usize, + contract_package_hash_size: usize, + label_ptr: usize, + label_size: usize, + num_new_urefs: u8, + existing_urefs_ptr: usize, + existing_urefs_size: usize, + output_size_ptr: usize, +): i32; +/** @hidden */ +@external("env", "disable_contract_version") +export declare function disable_contract_version( + contract_package_hash_ptr: usize, + contract_package_hash_size: usize, + contract_hash_ptr: usize, + contract_hash_size: usize, +): i32; \ No newline at end of file diff --git a/smart-contracts/contract-as/assembly/index.ts b/smart-contracts/contract-as/assembly/index.ts new file mode 100644 index 0000000000..e81c5b2c3f --- /dev/null +++ b/smart-contracts/contract-as/assembly/index.ts @@ -0,0 +1,631 @@ +import * as externals from "./externals"; +import {URef, AccessRights} from "./uref"; +import {Error, ErrorCode} from "./error"; +import {CLValue, CLType, CLTypeTag} from "./clvalue"; +import {Key, AccountHash} from "./key"; +import {Pair} from "./pair"; +import {toBytesString, + toBytesVecT, + fromBytesMap, + fromBytesString, + toBytesStringList, + Result, + Ref, + toBytesMap, + toBytesVecT, + fromBytesArray} from "./bytesrepr"; +import {KEY_UREF_SERIALIZED_LENGTH, UREF_ADDR_LENGTH, KEY_HASH_LENGTH} from "./constants"; +import {RuntimeArgs} from "./runtime_args"; +import {encodeUTF8} from "./utils"; +import {Option} from "./option"; + +// NOTE: interfaces aren't supported in AS yet: https://github.com/AssemblyScript/assemblyscript/issues/146#issuecomment-399130960 +// interface ToBytes { +// fromBytes(bytes: Uint8Array): ToBytes; +// } + +/** + * Length of address + */ +const ADDR_LENGTH = 32; + +/** + * System contract types. + */ +export const enum SystemContract { + /** + * Mint contract. + */ + Mint = 0, + /** + * Proof of Stake contract. + */ + ProofOfStake = 1, + /** + * Standard Payment contract. + */ + StandardPayment = 2, +} + +/** + * Returns size in bytes of I-th parameter + * + * @internal + * @param i I-th parameter + */ +export function getNamedArgSize(name: String): Ref | null { + let size = new Array(1); + size[0] = 0; + + const nameBuf = encodeUTF8(name); + let ret = externals.get_named_arg_size(nameBuf.dataStart, nameBuf.length, size.dataStart); + const error = Error.fromResult(ret); + if (error !== null) { + if (error.value() == ErrorCode.MissingArgument) { + return null; + } + error.revert(); + return >unreachable(); + } + const sizeU32 = changetype(size[0]); + return new Ref(sizeU32); +} + +/** + * Returns the i-th argument passed to the host for the current module + * invocation. + * + * Note that this is only relevant to contracts stored on-chain since a + * contract deployed directly is not invoked with any arguments. + * + * @param i I-th parameter + * @returns Array of bytes with ABI serialized argument. A null value if + * given parameter is not present. + */ +export function getNamedArg(name: String): Uint8Array { + let arg_size = getNamedArgSize(name); + if (arg_size == null) { + Error.fromErrorCode(ErrorCode.MissingArgument).revert(); + return unreachable(); + } + let nameBytes = encodeUTF8(name); + + let arg_size_u32 = changetype(arg_size.value); + let data = new Uint8Array(arg_size_u32); + let ret = externals.get_named_arg(nameBytes.dataStart, nameBytes.length, data.dataStart, arg_size_u32); + const error = Error.fromResult(ret); + if (error !== null) { + error.revert(); + return unreachable(); + } + return data; +} + +/** + * Reads a given amount of bytes from a host buffer + * + * @internal + * @param count Number of bytes + * @returns A byte array with bytes received, otherwise a null in case of + * errors. + */ +export function readHostBuffer(count: u32): Uint8Array { + let result = new Uint8Array(count); + let resultSize = new Uint32Array(1); + + let ret = externals.read_host_buffer(result.dataStart, result.length, resultSize.dataStart); + const error = Error.fromResult(ret); + if (error !== null) { + error.revert(); + return unreachable(); + } + return result; +} + +/** + * Returns an [[URef]] for a given system contract + * + * @param system_contract System contract variant + * @returns A valid [[URef]] that points at system contract, otherwise null. + */ +export function getSystemContract(systemContract: SystemContract): Uint8Array { + let data = new Uint8Array(32); + let ret = externals.get_system_contract(systemContract, data.dataStart, data.length); + const error = Error.fromResult(ret); + if (error !== null) { + error.revert(); + return unreachable(); + } + return data; +} + +/** + * Calls the given stored contract, passing the given arguments to it. + * + * If the stored contract calls [[ret]], then that value is returned from [[callContract]]. If the + * stored contract calls [[Error.revert]], then execution stops and [[callContract]] doesn't return. + * Otherwise [[callContract]] returns null. + * + * @param contractHash A key under which a contract is stored + * @param args A list of values + * @returns Bytes of the contract's return value. + */ +export function callContract(contractHash: Uint8Array, entryPointName: String, runtimeArgs: RuntimeArgs): Uint8Array { + let argBytes = runtimeArgs.toBytes(); + let entryPointNameBytes = toBytesString(entryPointName); + + let resultSize = new Uint32Array(1); + resultSize.fill(0); + + let ret = externals.call_contract( + contractHash.dataStart, + contractHash.length, + entryPointNameBytes.dataStart, + entryPointNameBytes.length, + argBytes.dataStart, + argBytes.length, + resultSize.dataStart, + ); + const error = Error.fromResult(ret); + if (error !== null) { + error.revert(); + return unreachable(); + } + let hostBufSize = resultSize[0]; + if (hostBufSize > 0) { + return readHostBuffer(hostBufSize); + } else { + return new Uint8Array(0); + } +} + +/** + * Stores the given [[Key]] under a given name in the current context's named keys. + * + * The current context is either the caller's account or a stored contract + * depending on whether the currently-executing module is a direct call or a + * sub-call respectively. + * + * @category Runtime + */ +export function putKey(name: String, key: Key): void { + var nameBytes = toBytesString(name); + var keyBytes = key.toBytes(); + externals.put_key( + nameBytes.dataStart, + nameBytes.length, + keyBytes.dataStart, + keyBytes.length + ); +} + +/** + * Removes the [[Key]] stored under `name` in the current context's named keys. + * + * The current context is either the caller's account or a stored contract depending on whether the + * currently-executing module is a direct call or a sub-call respectively. + * + * @param name Name of the key in current context's named keys + * @returns An instance of [[Key]] if it exists, or a `null` otherwise. + */ +export function getKey(name: String): Key | null { + var nameBytes = toBytesString(name); + let keyBytes = new Uint8Array(KEY_UREF_SERIALIZED_LENGTH); + let resultSize = new Uint32Array(1); + let ret = externals.get_key( + nameBytes.dataStart, + nameBytes.length, + keyBytes.dataStart, + keyBytes.length, + resultSize.dataStart, + ); + const error = Error.fromResult(ret); + if (error !== null) { + if (error.value() == ErrorCode.MissingKey) { + return null; + } + error.revert(); + return unreachable(); + } + let key = Key.fromBytes(keyBytes.slice(0, resultSize[0])); // total guess + return key.unwrap(); +} + +/** + * Returns the given [[CLValue]] to the host, terminating the currently + * running module. + * + * Note this function is only relevant to contracts stored on chain which are + * invoked via [[callContract]] and can thus return a value to their caller. + * The return value of a directly deployed contract is never used. + */ +export function ret(value: CLValue): void { + const valueBytes = value.toBytes(); + externals.ret( + valueBytes.dataStart, + valueBytes.length + ); + unreachable(); +} + +/** + * Returns `true` if `name` exists in the current context's named keys. + * + * The current context is either the caller's account or a stored contract depending on whether the + * currently-executing module is a direct call or a sub-call respectively. + * + * @param name Name of the key + */ +export function hasKey(name: String): bool { + const nameBytes = toBytesString(name); + let ret = externals.has_key(nameBytes.dataStart, nameBytes.length); + return ret == 0; +} + +/** + * Returns the current block time. + */ +export function getBlockTime(): u64 { + let bytes = new Uint64Array(1); + externals.get_blocktime(bytes.dataStart); + return bytes[0]; +} + +/** + * Returns the caller of the current context, i.e. the [[AccountHash]] of the + * account which made the deploy request. + */ +export function getCaller(): AccountHash { + let outputSize = new Uint32Array(1); + let ret = externals.get_caller(outputSize.dataStart); + const error = Error.fromResult(ret); + if (error !== null) { + error.revert(); + return unreachable(); + } + const accountHashBytes = readHostBuffer(outputSize[0]); + const accountHashResult = AccountHash.fromBytes(accountHashBytes); + if (accountHashResult.hasError()) { + Error.fromErrorCode(ErrorCode.Deserialize).revert(); + return unreachable(); + } + return accountHashResult.value; +} + +/** + * The phase in which a given contract is executing. + */ +export enum Phase { + /** + * Set while committing the genesis or upgrade configurations. + */ + System = 0, + /** + * Set while executing the payment code of a deploy. + */ + Payment = 1, + /** + * Set while executing the session code of a deploy. + */ + Session = 2, + /** + * Set while finalizing payment at the end of a deploy. + */ + FinalizePayment = 3, +} + +/** + * Returns the current [[Phase]]. + */ +export function getPhase(): Phase { + let bytes = new Uint8Array(1); + externals.get_phase(bytes.dataStart); + const phase = bytes[0]; + return phase; +} + +/** + * Removes the [[Key]] stored under `name` in the current context's named keys. + * + * The current context is either the caller's account or a stored contract depending on whether the + * currently-executing module is a direct call or a sub-call respectively. + */ +export function removeKey(name: String): void{ + var nameBytes = toBytesString(name); + externals.remove_key(nameBytes.dataStart, nameBytes.length); +} + +/** + * Returns the named keys of the current context. + * + * The current context is either the caller's account or a stored contract depending on whether the + * currently-executing module is a direct call or a sub-call respectively. + * + * @returns An array of String and [[Key]] pairs + */ +export function listNamedKeys(): Array> { + let totalKeys = new Uint32Array(1); + let resultSize = new Uint32Array(1); + + const res = externals.load_named_keys(totalKeys.dataStart, resultSize.dataStart); + const error = Error.fromResult(res); + if (error !== null) { + error.revert(); + return >>unreachable(); + } + + if (totalKeys[0] == 0) { + return new Array>(); + } + + let mapBytes = readHostBuffer(resultSize[0]); + let maybeMap = fromBytesMap( + mapBytes, + fromBytesString, + Key.fromBytes); + + if (maybeMap.hasError()) { + Error.fromErrorCode(ErrorCode.Deserialize).revert(); + return >>unreachable(); + } + return maybeMap.value; +} + +const ENTRYPOINTACCESS_PUBLIC_TAG: u8 = 1; +const ENTRYPOINTACCESS_GROUPS_TAG: u8 = 2; + +export class EntryPointAccess { + constructor(public cachedBytes: Array) {} + toBytes(): Array { + return this.cachedBytes; + } +} + +export class PublicAccess extends EntryPointAccess { + constructor() { + super([ENTRYPOINTACCESS_PUBLIC_TAG]); + } +}; + +export class GroupAccess extends EntryPointAccess { + constructor(groups: String[]) { + let bytes: Array = [ENTRYPOINTACCESS_GROUPS_TAG]; + bytes = bytes.concat(toBytesStringList(groups)); + super(bytes); + } +}; + + +export enum EntryPointType { + Session = 0, + Contract = 1, +} + +export class EntryPoint { + constructor(public name: String, + public args: Array>, + public ret: CLType, + public access: EntryPointAccess, + public entry_point_type: EntryPointType) {} + + toBytes(): Array { + let nameBytes = toBytesString(this.name); + let toBytesCLType = function(clType: CLType): Array { return clType.toBytes(); }; + let argsBytes = toBytesMap(this.args, toBytesString, toBytesCLType); + let retBytes = this.ret.toBytes(); + let accessBytes = this.access.toBytes(); + let entryPointTypeBytes: Array = [this.entry_point_type]; + return nameBytes.concat(argsBytes).concat(retBytes).concat(accessBytes).concat(entryPointTypeBytes); + } +}; + +export class EntryPoints { + entryPoints: Array> = new Array>(); + addEntryPoint(entryPoint: EntryPoint): void { + this.entryPoints.push(new Pair(entryPoint.name, entryPoint)); + } + toBytes(): Array { + let toBytesEntryPoint = function(entryPoint: EntryPoint): Array { return entryPoint.toBytes(); }; + return toBytesMap(this.entryPoints, toBytesString, toBytesEntryPoint); + } +} + +/** + * A two-value structure that holds the result of [[createContractPackageAtHash]]. + */ +export class CreateContractPackageResult { + constructor(public packageHash: Uint8Array, public accessURef: URef) {} +} + +export function createContractPackageAtHash(): CreateContractPackageResult { + let hashAddr = new Uint8Array(KEY_HASH_LENGTH); + let urefAddr = new Uint8Array(UREF_ADDR_LENGTH); + externals.create_contract_package_at_hash(hashAddr.dataStart, urefAddr.dataStart); + return new CreateContractPackageResult( + hashAddr, + new URef(urefAddr, AccessRights.READ_ADD_WRITE), + ); +} + +export function newContract(entryPoints: EntryPoints, namedKeys: Array> | null = null, hashName: String | null = null, urefName: String | null = null): AddContractVersionResult { + let result = createContractPackageAtHash(); + if (hashName !== null) { + putKey(hashName, Key.fromHash(result.packageHash)); + } + if (urefName !== null) { + putKey(urefName, Key.fromURef(result.accessURef)); + } + + if (namedKeys === null) { + namedKeys = new Array>(); + } + + return addContractVersion( + result.packageHash, + entryPoints, + namedKeys, + ); +} + +export function callVersionedContract(packageHash: Uint8Array, contract_version: Option, entryPointName: String, runtimeArgs: RuntimeArgs): Uint8Array { + let entryPointBytes = toBytesString(entryPointName); + let argBytes = runtimeArgs.toBytes(); + let bytesWritten = new Uint32Array(1); + let bytesContractVersion = contract_version.toBytes(); + + let ret = externals.call_versioned_contract( + packageHash.dataStart, + packageHash.length, + bytesContractVersion.dataStart, + bytesContractVersion.length, + entryPointBytes.dataStart, + entryPointBytes.length, + argBytes.dataStart, + argBytes.length, + bytesWritten.dataStart, + ); + let err = Error.fromResult(ret); + if (err !== null) { + err.revert(); + } + if (bytesWritten[0] == 0) { + return new Uint8Array(0); + } + else { + return readHostBuffer(bytesWritten[0]); + } +} + +// Container for a result of contract version. +// Used as a replacement of non-existing tuples. +export class AddContractVersionResult { + constructor(public contractHash: Uint8Array, public contractVersion: u32) {} +} + +// Add new contract version. Requires a package hash, entry points and named keys. +// Result +export function addContractVersion(packageHash: Uint8Array, entryPoints: EntryPoints, namedKeys: Array>): AddContractVersionResult { + var versionPtr = new Uint32Array(1); + let entryPointsBytes = entryPoints.toBytes(); + let keyToBytes = function(key: Key): Array { return key.toBytes(); }; + let namedKeysBytes = toBytesMap(namedKeys, toBytesString, keyToBytes); + let keyBytes = new Uint8Array(32); + let totalBytes = new Uint32Array(1); + + let ret = externals.add_contract_version( + packageHash.dataStart, + packageHash.length, + versionPtr.dataStart, // output + entryPointsBytes.dataStart, + entryPointsBytes.length, + namedKeysBytes.dataStart, + namedKeysBytes.length, + keyBytes.dataStart, + keyBytes.length, + totalBytes.dataStart, + ); + const error = Error.fromResult(ret); + if (error !== null) { + error.revert(); + return unreachable(); + } + + const contractHash = keyBytes.slice(0, totalBytes[0]); + const contractVersion = versionPtr[0]; + return new AddContractVersionResult(contractHash, contractVersion); +} + +export function createContractUserGroup(packageHash: Uint8Array, label: String, newURefs: u8, existingURefs: Array): Array { + let labelBytes = toBytesString(label); + + // NOTE: AssemblyScript sometimes is fine with closures, and sometimes + // it generates unreachable code. Anonymous functions seems to be working + // consistently. + let toBytesURef = function(item: URef): Array { return item.toBytes(); } + let fromBytesURef = function(bytes: Uint8Array): Result { return URef.fromBytes(bytes); } + + let existingUrefBytes: Array = toBytesVecT(existingURefs, toBytesURef); + + let outputSize = new Uint32Array(1); + + let ret = externals.create_contract_user_group( + packageHash.dataStart, + packageHash.length, + labelBytes.dataStart, + labelBytes.length, + newURefs, + existingUrefBytes.dataStart, + existingUrefBytes.length, + outputSize.dataStart, + ); + + let err = Error.fromResult(ret); + if (err !== null) { + err.revert(); + return >unreachable(); + } + let bytes = readHostBuffer(outputSize[0]); + return fromBytesArray(bytes, fromBytesURef).unwrap(); +} + +export function removeContractUserGroup( + packageHash: Uint8Array, + label: String, +): void { + let label_bytes = toBytesString(label); + let ret = externals.remove_contract_user_group( + packageHash.dataStart, + packageHash.length, + label_bytes.dataStart, + label_bytes.length, + ); + let err = Error.fromResult(ret); + if (err !== null) { + err.revert(); + } +} + +export function extendContractUserGroupURefs( + packageHash: Uint8Array, + label: String, +): URef { + let label_bytes = toBytesString(label); + let size = new Uint32Array(1); + let ret = externals.provision_contract_user_group_uref( + packageHash.dataStart, + packageHash.length, + label_bytes.dataStart, + label_bytes.length, + size.dataStart, + ); + let err = Error.fromResult(ret); + if (err !== null) { + err.revert(); + } + let bytes = readHostBuffer(size[0]); + return URef.fromBytes(bytes).unwrap(); +} + +export function removeContractUserGroupURefs( + packageHash: Uint8Array, + label: String, + urefs: Array): void { + + let label_bytes = toBytesString(label); + + let encode = function(item: URef): Array { return item.toBytes(); }; + let urefsData = toBytesVecT(urefs, encode); + + let ret = externals.remove_contract_user_group_urefs( + packageHash.dataStart, + packageHash.length, + label_bytes.dataStart, + label_bytes.length, + urefsData.dataStart, + urefsData.length, + ); + let err = Error.fromResult(ret); + if (err !== null) { + err.revert(); + } +} \ No newline at end of file diff --git a/smart-contracts/contract-as/assembly/key.ts b/smart-contracts/contract-as/assembly/key.ts new file mode 100644 index 0000000000..da96490b06 --- /dev/null +++ b/smart-contracts/contract-as/assembly/key.ts @@ -0,0 +1,275 @@ +import * as externals from "./externals"; +import {readHostBuffer} from "."; +import {UREF_SERIALIZED_LENGTH} from "./constants"; +import {URef} from "./uref"; +import {CLValue} from "./clvalue"; +import {Error, ErrorCode} from "./error"; +import {checkTypedArrayEqual, typedToArray} from "./utils"; +import {Result, Ref, Error as BytesreprError} from "./bytesrepr"; + +/** + * Enum representing a variant of a [[Key]] - Account, Hash or URef. + */ +export enum KeyVariant { + /** The Account variant */ + ACCOUNT_ID = 0, + /** The Hash variant */ + HASH_ID = 1, + /** The URef variant */ + UREF_ID = 2, +} + +/** A cryptographic public key. */ +export class AccountHash { + /** + * Constructs a new `AccountHash`. + * + * @param bytes The bytes constituting the public key. + */ + constructor(public bytes: Uint8Array) {} + + /** Checks whether two `AccountHash`s are equal. */ + @operator("==") + equalsTo(other: AccountHash): bool { + return checkTypedArrayEqual(this.bytes, other.bytes); + } + + /** Checks whether two `AccountHash`s are not equal. */ + @operator("!=") + notEqualsTo(other: AccountHash): bool { + return !this.equalsTo(other); + } + + /** Deserializes a `AccountHash` from an array of bytes. */ + static fromBytes(bytes: Uint8Array): Result { + if (bytes.length < 32) { + return new Result(null, BytesreprError.EarlyEndOfStream, 0); + } + + let accountHashBytes = bytes.subarray(0, 32); + let accountHash = new AccountHash(accountHashBytes); + let ref = new Ref(accountHash); + return new Result(ref, BytesreprError.Ok, 32); + } + + /** Serializes a `AccountHash` into an array of bytes. */ + toBytes(): Array { + return typedToArray(this.bytes); + } +} + +/** + * The type under which data (e.g. [[CLValue]]s, smart contracts, user accounts) + * are indexed on the network. + */ +export class Key { + variant: KeyVariant; + hash: Uint8Array | null; + uref: URef | null; + account: AccountHash | null; + + /** Creates a `Key` from a given [[URef]]. */ + static fromURef(uref: URef): Key { + let key = new Key(); + key.variant = KeyVariant.UREF_ID; + key.uref = uref; + return key; + } + + /** Creates a `Key` from a given hash. */ + static fromHash(hash: Uint8Array): Key { + let key = new Key(); + key.variant = KeyVariant.HASH_ID; + key.hash = hash; + return key; + } + + /** Creates a `Key` from a [[]] representing an account. */ + static fromAccount(account: AccountHash): Key { + let key = new Key(); + key.variant = KeyVariant.ACCOUNT_ID; + key.account = account; + return key; + } + + /** + * Attempts to write `value` under a new Key::URef + * + * If a key is returned it is always of [[KeyVariant]].UREF_ID + */ + static create(value: CLValue): Key | null { + const valueBytes = value.toBytes(); + let urefBytes = new Uint8Array(UREF_SERIALIZED_LENGTH); + externals.new_uref( + urefBytes.dataStart, + valueBytes.dataStart, + valueBytes.length + ); + const urefResult = URef.fromBytes(urefBytes); + if (urefResult.hasError()) { + return null; + } + return Key.fromURef(urefResult.value); + } + + /** Deserializes a `Key` from an array of bytes. */ + static fromBytes(bytes: Uint8Array): Result { + if (bytes.length < 1) { + return new Result(null, BytesreprError.EarlyEndOfStream, 0); + } + const tag = bytes[0]; + let currentPos = 1; + + if (tag == KeyVariant.HASH_ID) { + var hashBytes = bytes.subarray(1, 32 + 1); + currentPos += 32; + + let key = Key.fromHash(hashBytes); + let ref = new Ref(key); + return new Result(ref, BytesreprError.Ok, currentPos); + } + else if (tag == KeyVariant.UREF_ID) { + var urefBytes = bytes.subarray(1); + var urefResult = URef.fromBytes(urefBytes); + if (urefResult.error != BytesreprError.Ok) { + return new Result(null, urefResult.error, 0); + } + let key = Key.fromURef(urefResult.value); + let ref = new Ref(key); + return new Result(ref, BytesreprError.Ok, currentPos + urefResult.position); + } + else if (tag == KeyVariant.ACCOUNT_ID) { + let accountHashBytes = bytes.subarray(1); + let accountHashResult = AccountHash.fromBytes(accountHashBytes); + if (accountHashResult.hasError()) { + return new Result(null, accountHashResult.error, currentPos); + } + currentPos += accountHashResult.position; + let key = Key.fromAccount(accountHashResult.value); + let ref = new Ref(key); + return new Result(ref, BytesreprError.Ok, currentPos); + } + else { + return new Result(null, BytesreprError.FormattingError, currentPos); + } + } + + /** Serializes a `Key` into an array of bytes. */ + toBytes(): Array { + if(this.variant == KeyVariant.UREF_ID){ + let bytes = new Array(); + bytes.push(this.variant) + bytes = bytes.concat((this.uref).toBytes()); + return bytes; + } + else if (this.variant == KeyVariant.HASH_ID) { + var hashBytes = this.hash; + let bytes = new Array(1 + hashBytes.length); + bytes[0] = this.variant; + for (let i = 0; i < hashBytes.length; i++) { + bytes[i + 1] = hashBytes[i]; + } + return bytes; + } + else if (this.variant == KeyVariant.ACCOUNT_ID) { + let bytes = new Array(); + bytes.push(this.variant); + bytes = bytes.concat((this.account).toBytes()); + return bytes; + } + else { + return >unreachable(); + } + } + + /** Checks whether the `Key` is of [[KeyVariant]].UREF_ID. */ + isURef(): bool { + return this.variant == KeyVariant.UREF_ID; + } + + /** Converts the `Key` into `URef`. */ + toURef(): URef { + return this.uref; + } + + /** Reads the data stored under this `Key`. */ + read(): Uint8Array | null { + const keyBytes = this.toBytes(); + let valueSize = new Uint8Array(1); + const ret = externals.read_value(keyBytes.dataStart, keyBytes.length, valueSize.dataStart); + const error = Error.fromResult(ret); + if (error != null) { + if (error.value() == ErrorCode.ValueNotFound) { + return null; + } + error.revert(); + return unreachable(); + } + // TODO: How can we have `read` that would deserialize host bytes into T? + return readHostBuffer(valueSize[0]); + } + + /** Stores a [[CLValue]] under this `Key`. */ + write(value: CLValue): void { + const keyBytes = this.toBytes(); + const valueBytes = value.toBytes(); + externals.write( + keyBytes.dataStart, + keyBytes.length, + valueBytes.dataStart, + valueBytes.length + ); + } + + /** Adds the given `CLValue` to a value already stored under this `Key`. */ + add(value: CLValue): void { + const keyBytes = this.toBytes(); + const valueBytes = value.toBytes(); + + externals.add( + keyBytes.dataStart, + keyBytes.length, + valueBytes.dataStart, + valueBytes.length + ); + } + + /** Checks whether two `Key`s are equal. */ + @operator("==") + equalsTo(other: Key): bool { + if (this.variant == KeyVariant.UREF_ID) { + if (other.variant == KeyVariant.UREF_ID) { + return this.uref == other.uref; + } + else { + return false; + } + } + else if (this.variant == KeyVariant.HASH_ID) { + if (other.variant == KeyVariant.HASH_ID) { + return checkTypedArrayEqual(this.hash, other.hash); + + } + else { + return false; + } + } + else if (this.variant == KeyVariant.ACCOUNT_ID) { + if (other.variant == KeyVariant.ACCOUNT_ID) { + return this.account == other.account; + } + else { + return false; + } + } + else { + return false; + } + } + + /** Checks whether two keys are not equal. */ + @operator("!=") + notEqualsTo(other: Key): bool { + return !this.equalsTo(other); + } +} diff --git a/smart-contracts/contract-as/assembly/local.ts b/smart-contracts/contract-as/assembly/local.ts new file mode 100644 index 0000000000..e49d67bfeb --- /dev/null +++ b/smart-contracts/contract-as/assembly/local.ts @@ -0,0 +1,53 @@ +//@ts-nocheck +import * as externals from "./externals"; +import {Error, ErrorCode} from "./error"; +import {CLValue} from "./clvalue"; +import {readHostBuffer} from "./index"; + +/** + * Reads the value under `key` in the context-local partition of global state. + * + * @category Storage + * @returns Returns bytes of serialized value, otherwise a null if given local key does not exists. + */ +export function readLocal(local: Uint8Array): Uint8Array | null { + let valueSize = new Uint8Array(1); + const ret = externals.read_value_local(local.dataStart, local.length, valueSize.dataStart); + if (ret == ErrorCode.ValueNotFound){ + return null; + } + const error = Error.fromResult(ret); + if (error != null) { + error.revert(); + return unreachable(); + } + return readHostBuffer(valueSize[0]); +} + +/** + * Writes `value` under `key` in the context-local partition of global state. + * @category Storage + */ +export function writeLocal(local: Uint8Array, value: CLValue): void { + const valueBytes = value.toBytes(); + externals.write_local( + local.dataStart, + local.length, + valueBytes.dataStart, + valueBytes.length + ); +} + +/** + * Adds `value` to the one currently under `key` in the context-local partition of global state. + * @category Storage + */ +export function addLocal(local: Uint8Array, value: CLValue): void { + const valueBytes = value.toBytes(); + externals.add_local( + local.dataStart, + local.length, + valueBytes.dataStart, + valueBytes.length + ); +} diff --git a/smart-contracts/contract-as/assembly/option.ts b/smart-contracts/contract-as/assembly/option.ts new file mode 100644 index 0000000000..5497e6d553 --- /dev/null +++ b/smart-contracts/contract-as/assembly/option.ts @@ -0,0 +1,78 @@ +const OPTION_TAG_NONE: u8 = 0; +const OPTION_TAG_SOME: u8 = 1; + +// TODO: explore Option (without interfaces to constrain T with, is it practical?) +/** + * A class representing an optional value, i.e. it might contain either a value of some type or + * no value at all. Similar to Rust's `Option` or Haskell's `Maybe`. + */ +export class Option{ + private bytes: Uint8Array | null; + + /** + * Constructs a new option containing the value of `bytes`. `bytes` can be `null`, which + * indicates no value. + */ + constructor(bytes: Uint8Array | null) { + this.bytes = bytes; + } + + /** + * Checks whether the `Option` contains no value. + * + * @returns True if the `Option` has no value. + */ + isNone(): bool{ + return this.bytes === null; + } + + /** + * Checks whether the `Option` contains a value. + * + * @returns True if the `Option` has some value. + */ + isSome() : bool{ + return this.bytes != null; + } + + /** + * Unwraps the `Option`, returning the inner value (or `null` if there was none). + * + * @returns The inner value, or `null` if there was none. + */ + unwrap(): Uint8Array | null{ + return this.bytes; + } + + /** + * Serializes the `Option` into an array of bytes. + */ + toBytes(): Array{ + if (this.bytes === null){ + let result = new Array(1); + result[0] = OPTION_TAG_NONE; + return result; + } + const bytes = this.bytes; + + let result = new Array(bytes.length + 1); + result[0] = OPTION_TAG_SOME; + for (let i = 0; i < bytes.length; i++) { + result[i+1] = bytes[i]; + } + + return result; + } + + /** + * Deserializes an array of bytes into an `Option`. + */ + static fromBytes(bytes: Uint8Array): Option{ + // check SOME / NONE flag at head + // TODO: what if length is exactly 1? + if (bytes.length >= 1 && bytes[0] == 1) + return new Option(bytes.subarray(1)); + + return new Option(null); + } +} diff --git a/smart-contracts/contract-as/assembly/pair.ts b/smart-contracts/contract-as/assembly/pair.ts new file mode 100644 index 0000000000..3ab74009ea --- /dev/null +++ b/smart-contracts/contract-as/assembly/pair.ts @@ -0,0 +1,41 @@ +/** + * A pair of values. + * + * @typeParam T1 The type of the first value. + * @typeParam T2 The type of the second value. + */ +export class Pair { + /** + * The first value in the pair. + */ + public first: T1; + /** + * The second value in the pair. + */ + public second: T2; + + /** + * Constructs the pair out of the two given values. + */ + constructor(first: T1, second: T2) { + this.first = first; + this.second = second; + } + + /** + * Checks whether two pairs are equal. The pairs are considered equal when both their first + * and second values are equal. + */ + @operator("==") + equalsTo(other: Pair): bool { + return this.first == other.first && this.second == other.second; + } + + /** + * Checks whether two pairs are not equal (the opposite of [[equalsTo]]). + */ + @operator("!=") + notEqualsTo(other: Pair): bool { + return !this.equalsTo(other); + } +} diff --git a/smart-contracts/contract-as/assembly/purse.ts b/smart-contracts/contract-as/assembly/purse.ts new file mode 100644 index 0000000000..fd72d31ac9 --- /dev/null +++ b/smart-contracts/contract-as/assembly/purse.ts @@ -0,0 +1,134 @@ +import * as externals from "./externals"; +import {readHostBuffer} from "./index"; +import {U512} from "./bignum"; +import {Error, ErrorCode} from "./error"; +import {UREF_SERIALIZED_LENGTH} from "./constants"; +import {URef} from "./uref"; + +/** + * The result of a successful transfer between purses. + */ +export enum TransferredTo { + /** + * The transfer operation resulted in an error. + */ + TransferError = -1, + /** + * The destination account already existed. + */ + ExistingAccount = 0, + /** + * The destination account was created. + */ + NewAccount = 1, +} + +/** + * Creates a new empty purse and returns its [[URef]], or a null in case a + * purse couldn't be created. + */ +export function createPurse(): URef { + let bytes = new Uint8Array(UREF_SERIALIZED_LENGTH); + let ret = externals.create_purse( + bytes.dataStart, + bytes.length + ); + let error = Error.fromResult(ret); + if (error !== null){ + error.revert(); + return unreachable(); + } + + let urefResult = URef.fromBytes(bytes); + if (urefResult.hasError()) { + Error.fromErrorCode(ErrorCode.PurseNotCreated).revert(); + return unreachable(); + } + + return urefResult.value; +} + +/** + * Returns the balance in motes of the given purse or a null if given purse + * is invalid. + */ +export function getPurseBalance(purse: URef): U512 | null { + let purseBytes = purse.toBytes(); + let balanceSize = new Array(1); + balanceSize[0] = 0; + + let retBalance = externals.get_balance( + purseBytes.dataStart, + purseBytes.length, + balanceSize.dataStart, + ); + + const error = Error.fromResult(retBalance); + if (error != null) { + if (error.value() == ErrorCode.InvalidPurse) { + return null; + } + error.revert(); + return unreachable(); + } + + let balanceBytes = readHostBuffer(balanceSize[0]); + let balanceResult = U512.fromBytes(balanceBytes); + return balanceResult.unwrap(); +} + +/** + * Transfers `amount` of motes from `source` purse to `target` account. + * If `target` does not exist it will be created. + * + * @param amount Amount is denominated in motes + * @returns This function will return a [[TransferredTo.TransferError]] in + * case of transfer error, in case of any other variant the transfer itself + * can be considered successful. + */ +export function transferFromPurseToAccount(sourcePurse: URef, targetAccount: Uint8Array, amount: U512): TransferredTo { + let purseBytes = sourcePurse.toBytes(); + let targetBytes = new Array(targetAccount.length); + for (let i = 0; i < targetAccount.length; i++) { + targetBytes[i] = targetAccount[i]; + } + + let amountBytes = amount.toBytes(); + + let ret = externals.transfer_from_purse_to_account( + purseBytes.dataStart, + purseBytes.length, + targetBytes.dataStart, + targetBytes.length, + amountBytes.dataStart, + amountBytes.length, + ); + + if (ret == TransferredTo.ExistingAccount) + return TransferredTo.ExistingAccount; + if (ret == TransferredTo.NewAccount) + return TransferredTo.NewAccount; + return TransferredTo.TransferError; +} + +/** + * Transfers `amount` of motes from `source` purse to `target` purse. If `target` does not exist + * the transfer fails. + * + * @returns This function returns non-zero value on error. + */ +export function transferFromPurseToPurse(sourcePurse: URef, targetPurse: URef, amount: U512): i32 { + let sourceBytes = sourcePurse.toBytes(); + let targetBytes = targetPurse.toBytes(); + let amountBytes = amount.toBytes(); + + let ret = externals.transfer_from_purse_to_purse( + sourceBytes.dataStart, + sourceBytes.length, + targetBytes.dataStart, + targetBytes.length, + amountBytes.dataStart, + amountBytes.length, + ); + return ret; +} diff --git a/smart-contracts/contract-as/assembly/runtime_args.ts b/smart-contracts/contract-as/assembly/runtime_args.ts new file mode 100644 index 0000000000..4850e2ed8b --- /dev/null +++ b/smart-contracts/contract-as/assembly/runtime_args.ts @@ -0,0 +1,27 @@ +import {CLValue} from "./clvalue"; +import { Pair } from "./pair"; +import { toBytesU32, toBytesString } from "./bytesrepr"; + +/** + * Implements a collection of runtime arguments. + */ +export class RuntimeArgs { + constructor(public arguments: Pair[] = []) {} + + static fromArray(pairs: Pair[]): RuntimeArgs { + return new RuntimeArgs(pairs); + } + + toBytes(): Array { + let bytes : u8[] = toBytesU32(this.arguments.length); + let args = this.arguments; + for (var i = 0; i < args.length; i++) { + let pair = args[i]; + const argNameBytes = toBytesString(pair.first); + bytes = bytes.concat(argNameBytes); + const argValueBytes = pair.second.toBytes(); + bytes = bytes.concat(argValueBytes); + } + return bytes; + } +} diff --git a/smart-contracts/contract-as/assembly/tsconfig.json b/smart-contracts/contract-as/assembly/tsconfig.json new file mode 100644 index 0000000000..05dd1cb45d --- /dev/null +++ b/smart-contracts/contract-as/assembly/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ], + "typedocOptions": { + "mode": "file", + "out": "docs" + } +} diff --git a/smart-contracts/contract-as/assembly/unit.ts b/smart-contracts/contract-as/assembly/unit.ts new file mode 100644 index 0000000000..21fa24931e --- /dev/null +++ b/smart-contracts/contract-as/assembly/unit.ts @@ -0,0 +1,19 @@ +/** + * A class representing the unit type, i.e. a type that has no values (equivalent to eg. `void` in + * C or `()` in Rust). + */ +export class Unit{ + /** + * Serializes a [[Unit]] - returns an empty array. + */ + toBytes(): Array { + return new Array(0); + } + + /** + * Deserializes a [[Unit]] - returns a new [[Unit]]. + */ + static fromBytes(bytes: Uint8Array): Unit{ + return new Unit(); + } +} diff --git a/smart-contracts/contract-as/assembly/uref.ts b/smart-contracts/contract-as/assembly/uref.ts new file mode 100644 index 0000000000..38ede74fdf --- /dev/null +++ b/smart-contracts/contract-as/assembly/uref.ts @@ -0,0 +1,142 @@ +import {Error, Result, Ref} from "./bytesrepr"; +import {UREF_ADDR_LENGTH} from "./constants"; +import {checkTypedArrayEqual, typedToArray} from "./utils"; +import {is_valid_uref, revert} from "./externals"; + +/** + * A set of bitflags that defines access rights associated with a [[URef]]. + */ +export enum AccessRights{ + /** + * No permissions + */ + NONE = 0x0, + /** + * Permission to read the value under the associated [[URef]]. + */ + READ = 0x1, + /** + * Permission to write a value under the associated [[URef]]. + */ + WRITE = 0x2, + /** + * Permission to read or write the value under the associated [[URef]]. + */ + READ_WRITE = 0x3, + /** + * Permission to add to the value under the associated [[URef]]. + */ + ADD = 0x4, + /** + * Permission to read or add to the value under the associated [[URef]]. + */ + READ_ADD = 0x5, + /** + * Permission to add to, or write the value under the associated [[URef]]. + */ + ADD_WRITE = 0x6, + /** + * Permission to read, add to, or write the value under the associated [[URef]]. + */ + READ_ADD_WRITE = 0x07, +} + +/** + * Represents an unforgeable reference, containing an address in the network's global storage and + * the [[AccessRights]] of the reference. + * + * A [[URef]] can be used to index entities such as [[CLValue]]s, or smart contracts. + */ +export class URef { + /** + * Representation of URef address. + */ + private bytes: Uint8Array; + private accessRights: AccessRights + + /** + * Constructs new instance of URef. + * @param bytes Bytes representing address of the URef. + * @param accessRights Access rights flag. Use [[AccessRights.NONE]] to indicate no permissions. + */ + constructor(bytes: Uint8Array, accessRights: AccessRights) { + this.bytes = bytes; + this.accessRights = accessRights; + } + + /** + * Returns the address of this URef as an array of bytes. + * + * @returns A byte array with a length of 32. + */ + public getBytes(): Uint8Array { + return this.bytes; + } + + /** + * Returns the access rights of this [[URef]]. + */ + public getAccessRights(): AccessRights { + return this.accessRights; + } + + /** + * Validates uref against named keys. + */ + public isValid(): boolean{ + const urefBytes = this.toBytes(); + let ret = is_valid_uref( + urefBytes.dataStart, + urefBytes.length + ); + return ret !== 0; + } + + /** + * Deserializes a new [[URef]] from bytes. + * @param bytes Input bytes. Requires at least 33 bytes to properly deserialize an [[URef]]. + */ + static fromBytes(bytes: Uint8Array): Result { + if (bytes.length < 33) { + return new Result(null, Error.EarlyEndOfStream, 0); + } + + let urefBytes = bytes.subarray(0, UREF_ADDR_LENGTH); + let currentPos = 33; + + let accessRights = bytes[UREF_ADDR_LENGTH]; + let uref = new URef(urefBytes, accessRights); + let ref = new Ref(uref); + return new Result(ref, Error.Ok, currentPos); + } + + /** + * Serializes the URef into an array of bytes that represents it in the CasperLabs serialization + * format. + */ + toBytes(): Array { + let result = typedToArray(this.bytes); + result.push(this.accessRights); + return result; + } + + /** + * The equality operator. + * + * @returns True if `this` and `other` are equal, false otherwise. + */ + @operator("==") + equalsTo(other: URef): bool { + return checkTypedArrayEqual(this.bytes, other.bytes) && this.accessRights == other.accessRights; + } + + /** + * The not-equal operator. + * + * @returns False if `this` and `other` are equal, true otherwise. + */ + @operator("!=") + notEqualsTo(other: URef): bool { + return !this.equalsTo(other); + } +} diff --git a/smart-contracts/contract-as/assembly/utils.ts b/smart-contracts/contract-as/assembly/utils.ts new file mode 100644 index 0000000000..44d69e6e39 --- /dev/null +++ b/smart-contracts/contract-as/assembly/utils.ts @@ -0,0 +1,69 @@ +/** + * Encodes an UTF8 string into bytes. + * @param str Input string. + */ +export function encodeUTF8(str: String): Uint8Array { + let utf8Bytes = String.UTF8.encode(str); + return Uint8Array.wrap(utf8Bytes); +} + +/** Converts typed array to array */ +export function typedToArray(arr: Uint8Array): Array { + let result = new Array(arr.length); + for (let i = 0; i < arr.length; i++) { + result[i] = arr[i]; + } + return result; +} + +/** Converts array to typed array */ +export function arrayToTyped(arr: Array): Uint8Array { + let result = new Uint8Array(arr.length); + for (let i = 0; i < arr.length; i++) { + result[i] = arr[i]; + } + return result; +} + +/** Checks if items in two unordered arrays are equal */ +export function checkItemsEqual(a: Array, b: Array): bool { + for (let i = 0; i < a.length; i++) { + const idx = b.indexOf(a[i]); + if (idx == -1) { + return false; + } + b.splice(idx, 1); + } + return b.length === 0; +} + +/** Checks if two ordered arrays are equal */ +export function checkArraysEqual(a: Array, b: Array, len: i32 = 0): bool { + if (!len) { + len = a.length; + if (len != b.length) return false; + if (a === b) return true; + } + for (let i = 0; i < len; i++) { + if (isFloat()) { + if (isNaN(a[i]) && isNaN(b[i])) continue; + } + if (a[i] != b[i]) return false; + } + return true; +} + + +/** Checks if two ordered arrays are equal */ +export function checkTypedArrayEqual(a: Uint8Array, b: Uint8Array, len: i32 = 0): bool { + if (!len) { + len = a.length; + if (len != b.length) return false; + if (a === b) return true; + } + for (let i = 0; i < len; i++) { + if (a[i] != b[i]) return false; + } + return true; +} + diff --git a/smart-contracts/contract-as/build/.gitignore b/smart-contracts/contract-as/build/.gitignore new file mode 100644 index 0000000000..870a503c72 --- /dev/null +++ b/smart-contracts/contract-as/build/.gitignore @@ -0,0 +1,4 @@ +*.wasm +*.wasm.map +*.asm.js +*.wat diff --git a/smart-contracts/contract-as/index.js b/smart-contracts/contract-as/index.js new file mode 100644 index 0000000000..7201a972a0 --- /dev/null +++ b/smart-contracts/contract-as/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/optimized.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contract-as/package-lock.json b/smart-contracts/contract-as/package-lock.json new file mode 100644 index 0000000000..bb8c47f226 --- /dev/null +++ b/smart-contracts/contract-as/package-lock.json @@ -0,0 +1,4521 @@ +{ + "name": "@casperlabs/contract", + "version": "0.5.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@assemblyscript/loader": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", + "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==", + "dev": true + }, + "@ava/babel-plugin-throws-helper": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@ava/babel-plugin-throws-helper/-/babel-plugin-throws-helper-4.0.0.tgz", + "integrity": "sha512-3diBLIVBPPh3j4+hb5lo0I1D+S/O/VDJPI4Y502apBxmwEqjyXG4gTSPFUlm41sSZeZzMarT/Gzovw9kV7An0w==", + "dev": true + }, + "@ava/babel-preset-stage-4": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@ava/babel-preset-stage-4/-/babel-preset-stage-4-4.0.0.tgz", + "integrity": "sha512-lZEV1ZANzfzSYBU6WHSErsy7jLPbD1iIgAboASPMcKo7woVni5/5IKWeT0RxC8rY802MFktur3OKEw2JY1Tv2w==", + "dev": true, + "requires": { + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-dynamic-import": "^7.5.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/plugin-transform-modules-commonjs": "^7.5.0" + } + }, + "@ava/babel-preset-transform-test-files": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ava/babel-preset-transform-test-files/-/babel-preset-transform-test-files-6.0.0.tgz", + "integrity": "sha512-8eKhFzZp7Qcq1VLfoC75ggGT8nQs9q8fIxltU47yCB7Wi7Y8Qf6oqY1Bm0z04fIec24vEgr0ENhDHEOUGVDqnA==", + "dev": true, + "requires": { + "@ava/babel-plugin-throws-helper": "^4.0.0", + "babel-plugin-espower": "^3.0.1" + } + }, + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.5.tgz", + "integrity": "sha512-M42+ScN4+1S9iB6f+TL7QBpoQETxbclx+KNoKJABghnKYE+fMzSGqst0BZJc8CpI625bwPwYgUyRvxZ+0mZzpw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.7.4", + "@babel/helpers": "^7.7.4", + "@babel/parser": "^7.7.5", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "json5": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", + "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", + "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.4.tgz", + "integrity": "sha512-2BQmQgECKzYKFPpiycoF9tlb5HA4lrVyAmLLVK177EcQAqjVLciUb2/R+n1boQ9y5ENV3uz2ZqiNw7QMBBw1Og==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.4.tgz", + "integrity": "sha512-Mt+jBKaxL0zfOIWrfQpnfYCN7/rS6GKx6CCCfuoqVVd+17R8zNDlzVYmIi9qyb2wOk002NsmSTDymkIygDUH7A==", + "dev": true, + "requires": { + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" + } + }, + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-module-imports": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz", + "integrity": "sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz", + "integrity": "sha512-A7pSxyJf1gN5qXVcidwLWydjftUN878VkalhXX5iQDuGyiGK3sOrrKKHF4/A4fwHtnsotv/NipwAeLzY4KQPvw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.7.4", + "@babel/helper-simple-access": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4", + "lodash": "^4.17.13" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", + "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", + "dev": true, + "requires": { + "lodash": "^4.17.13" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.4.tgz", + "integrity": "sha512-Sk4xmtVdM9sA/jCI80f+KS+Md+ZHIpjuqmYPk1M7F/upHou5e4ReYmExAiu6PVe65BhJPZA2CY9x9k4BqE5klw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.7.4", + "@babel/helper-wrap-function": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.7.4.tgz", + "integrity": "sha512-zK7THeEXfan7UlWsG2A6CI/L9jVnI5+xxKZOdej39Y0YtDYKx9raHk5F2EtK9K8DHRTihYwg20ADt9S36GR78A==", + "dev": true, + "requires": { + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", + "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-wrap-function": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.7.4.tgz", + "integrity": "sha512-VsfzZt6wmsocOaVU0OokwrIytHND55yvyT4BPB9AIIgwr8+x7617hetdJTsuGwygN5RC6mxA9EJztTjuwm2ofg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helpers": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.7.4.tgz", + "integrity": "sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg==", + "dev": true, + "requires": { + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.4.tgz", + "integrity": "sha512-1ypyZvGRXriY/QP668+s8sFr2mqinhkRDMPSQLNghCQE+GAkFtp+wkHVvg2+Hdki8gwP+NFzJBJ/N1BfzCCDEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.7.4", + "@babel/plugin-syntax-async-generators": "^7.7.4" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.4.tgz", + "integrity": "sha512-StH+nGAdO6qDB1l8sZ5UBV8AC3F2VW2I8Vfld73TMKyptMU9DY5YsJAS8U81+vEtxcH3Y/La0wG0btDrhpnhjQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.7.4" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.7.4.tgz", + "integrity": "sha512-DyM7U2bnsQerCQ+sejcTNZh8KQEUuC3ufzdnVnSiUv/qoGJp2Z3hanKL18KDhsBT5Wj6a7CMT5mdyCNJsEaA9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.7.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.7.4.tgz", + "integrity": "sha512-Li4+EjSpBgxcsmeEF8IFcfV/+yJGxHXDirDkEoyFjumuwbmfCVHUt0HuowD/iGM7OhIRyXJH9YXxqiH6N815+g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.7.4.tgz", + "integrity": "sha512-jHQW0vbRGvwQNgyVxwDh4yuXu4bH1f5/EICJLAhl1SblLs2CDhrsmCk+v5XLdE9wxtAFRyxx+P//Iw+a5L/tTg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.7.4.tgz", + "integrity": "sha512-4ZSuzWgFxqHRE31Glu+fEr/MirNZOMYmD/0BhBWyLyOOQz/gTAl7QmWm2hX1QxEIXsr2vkdlwxIzTyiYRC4xcQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.4.tgz", + "integrity": "sha512-mk0cH1zyMa/XHeb6LOTXTbG7uIJ8Rrjlzu91pUx/KS3JpcgaTDwMS8kM+ar8SLOvlL2Lofi4CGBAjCo3a2x+lw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.5.tgz", + "integrity": "sha512-9Cq4zTFExwFhQI6MT1aFxgqhIsMWQWDVwOgLzl7PTWJHsNaqFvklAU+Oz6AQLAS0dJKTwZSOCo20INwktxpi3Q==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.7.5", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.7.4", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/traverse": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", + "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.7.4", + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@concordance/react": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@concordance/react/-/react-2.0.0.tgz", + "integrity": "sha512-huLSkUuM2/P+U0uy2WwlKuixMsTODD8p4JVQBI4VKeopkiN0C7M3N9XYVawb4M+4spN5RrO/eLhk7KoQX6nsfA==", + "dev": true, + "requires": { + "arrify": "^1.0.1" + }, + "dependencies": { + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + } + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@textlint/ast-node-types": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-4.2.5.tgz", + "integrity": "sha512-+rEx4jLOeZpUcdvll7jEg/7hNbwYvHWFy4IGW/tk2JdbyB3SJVyIP6arAwzTH/sp/pO9jftfyZnRj4//sLbLvQ==", + "dev": true + }, + "@textlint/markdown-to-ast": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@textlint/markdown-to-ast/-/markdown-to-ast-6.0.9.tgz", + "integrity": "sha512-hfAWBvTeUGh5t5kTn2U3uP3qOSM1BSrxzl1jF3nn0ywfZXpRBZr5yRjXnl4DzIYawCtZOshmRi/tI3/x4TE1jQ==", + "dev": true, + "requires": { + "@textlint/ast-node-types": "^4.0.3", + "debug": "^2.1.3", + "remark-frontmatter": "^1.2.0", + "remark-parse": "^5.0.0", + "structured-source": "^3.0.2", + "traverse": "^0.6.6", + "unified": "^6.1.6" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "12.12.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.19.tgz", + "integrity": "sha512-OXw80IpKyLeuZ5a8r2XCxVNnRAtS3lRDHBleSUQmbgu3C6eKqRsz7/5XNBU0EvK0RTVfotvYFgvRwwe2jeoiKw==", + "dev": true + }, + "anchor-markdown-header": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/anchor-markdown-header/-/anchor-markdown-header-0.5.7.tgz", + "integrity": "sha1-BFBj125qH5zTJ6V6ASaqD97Dcac=", + "dev": true, + "requires": { + "emoji-regex": "~6.1.0" + }, + "dependencies": { + "emoji-regex": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.3.tgz", + "integrity": "sha1-7HmjlpsC0uzytyJUJ5v5m8eoOTI=", + "dev": true + } + } + }, + "ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "dev": true, + "requires": { + "string-width": "^3.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, + "ansi-escapes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.2.tgz", + "integrity": "sha512-+ytCkGcBtHZ3V2r2Z06AncYO8jz46UEamcspGoU8lHcEbpn6J77QK0vdWvChsclg/tM5XIJC5tnjmPp7Eq6Obg==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array-uniq": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-2.1.0.tgz", + "integrity": "sha512-bdHxtev7FN6+MXI1YFW0Q8mQ8dTJc2S8AMfju+ZR77pbg2yAdVyDlwkaUI7Har0LyOMRFPHrJ9lYdyjZZswdlQ==", + "dev": true + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, + "assemblyscript": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.10.0.tgz", + "integrity": "sha512-ErUNhHboD+zsB4oG6X1YICDAIo27Gq7LeNX6jVe+Q0W5cI51/fHwC8yJ68IukqvupmZgYPdp1JqqRXlS+BrUfA==", + "dev": true, + "requires": { + "binaryen": "93.0.0-nightly.20200514", + "long": "^4.0.0" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "ava": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ava/-/ava-2.4.0.tgz", + "integrity": "sha512-CQWtzZZZeU2g4StojRv6MO9RIRi4sLxGSB9+3C3hv0ttUEG1tkJLTLyrBQeFS4WEeK12Z4ovE3f2iPVhSy8elA==", + "dev": true, + "requires": { + "@ava/babel-preset-stage-4": "^4.0.0", + "@ava/babel-preset-transform-test-files": "^6.0.0", + "@babel/core": "^7.6.0", + "@babel/generator": "^7.6.0", + "@concordance/react": "^2.0.0", + "ansi-escapes": "^4.2.1", + "ansi-styles": "^4.1.0", + "arr-flatten": "^1.1.0", + "array-union": "^2.1.0", + "array-uniq": "^2.1.0", + "arrify": "^2.0.1", + "bluebird": "^3.5.5", + "chalk": "^2.4.2", + "chokidar": "^3.0.2", + "chunkd": "^1.0.0", + "ci-parallel-vars": "^1.0.0", + "clean-stack": "^2.2.0", + "clean-yaml-object": "^0.1.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.0.0", + "code-excerpt": "^2.1.1", + "common-path-prefix": "^1.0.0", + "concordance": "^4.0.0", + "convert-source-map": "^1.6.0", + "currently-unhandled": "^0.4.1", + "debug": "^4.1.1", + "del": "^4.1.1", + "dot-prop": "^5.1.0", + "emittery": "^0.4.1", + "empower-core": "^1.2.0", + "equal-length": "^1.0.0", + "escape-string-regexp": "^2.0.0", + "esm": "^3.2.25", + "figures": "^3.0.0", + "find-up": "^4.1.0", + "get-port": "^5.0.0", + "globby": "^10.0.1", + "ignore-by-default": "^1.0.0", + "import-local": "^3.0.2", + "indent-string": "^4.0.0", + "is-ci": "^2.0.0", + "is-error": "^2.2.2", + "is-observable": "^2.0.0", + "is-plain-object": "^3.0.0", + "is-promise": "^2.1.0", + "lodash": "^4.17.15", + "loud-rejection": "^2.1.0", + "make-dir": "^3.0.0", + "matcher": "^2.0.0", + "md5-hex": "^3.0.1", + "meow": "^5.0.0", + "micromatch": "^4.0.2", + "ms": "^2.1.2", + "observable-to-promise": "^1.0.0", + "ora": "^3.4.0", + "package-hash": "^4.0.0", + "pkg-conf": "^3.1.0", + "plur": "^3.1.1", + "pretty-ms": "^5.0.0", + "require-precompiled": "^0.1.0", + "resolve-cwd": "^3.0.0", + "slash": "^3.0.0", + "source-map-support": "^0.5.13", + "stack-utils": "^1.0.2", + "strip-ansi": "^5.2.0", + "strip-bom-buf": "^2.0.0", + "supertap": "^1.0.0", + "supports-color": "^7.0.0", + "trim-off-newlines": "^1.0.1", + "trim-right": "^1.0.1", + "unique-temp-dir": "^1.0.0", + "update-notifier": "^3.0.1", + "write-file-atomic": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.0.tgz", + "integrity": "sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", + "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-espower": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-espower/-/babel-plugin-espower-3.0.1.tgz", + "integrity": "sha512-Ms49U7VIAtQ/TtcqRbD6UBmJBUCSxiC3+zPc+eGqxKUIFO1lTshyEDRUjhoAbd2rWfwYf3cZ62oXozrd8W6J0A==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "call-matcher": "^1.0.0", + "core-js": "^2.0.0", + "espower-location-detector": "^1.0.0", + "espurify": "^1.6.0", + "estraverse": "^4.1.1" + } + }, + "backbone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz", + "integrity": "sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ==", + "dev": true, + "requires": { + "underscore": ">=1.8.3" + } + }, + "bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "binaryen": { + "version": "93.0.0-nightly.20200514", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-93.0.0-nightly.20200514.tgz", + "integrity": "sha512-SRRItmNvhRVfoWWbRloO4i8IqkKH8rZ7/0QWRgLpM3umupK8gBpo9MY7Zp3pDysRSp+rVoqxvM5x4tFyCSa9zw==", + "dev": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "blueimp-md5": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.12.0.tgz", + "integrity": "sha512-zo+HIdIhzojv6F1siQPqPFROyVy7C50KzHv/k/Iz+BtvtVzSHXiMXOpq2wCfNkeBqdCv+V8XOV96tsEt2W/3rQ==", + "dev": true + }, + "boundary": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-1.0.1.tgz", + "integrity": "sha1-TWfcJgLAzBbdm85+v4fpSCkPWBI=", + "dev": true + }, + "boxen": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-3.2.0.tgz", + "integrity": "sha512-cU4J/+NodM3IHdSL2yN8bqYqnmlBTidDR4RC7nJs61ZmtGz8VZzM3HLQX0zY5mrSmPtR3xWwsq2jOUQqFZN8+A==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^2.4.2", + "cli-boxes": "^2.2.0", + "string-width": "^3.0.0", + "term-size": "^1.2.0", + "type-fest": "^0.3.0", + "widest-line": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "call-matcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.1.0.tgz", + "integrity": "sha512-IoQLeNwwf9KTNbtSA7aEBb1yfDbdnzwjCetjkC8io5oGeOmK2CBNdg0xr+tadRYKO0p7uQyZzvon0kXlZbvGrw==", + "dev": true, + "requires": { + "core-js": "^2.0.0", + "deep-equal": "^1.0.0", + "espurify": "^1.6.0", + "estraverse": "^4.0.0" + } + }, + "call-signature": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/call-signature/-/call-signature-0.0.2.tgz", + "integrity": "sha1-qEq8glpV70yysCi9dOIFpluaSZY=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true + }, + "character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true + }, + "character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true + }, + "chokidar": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", + "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.3.0" + } + }, + "chunkd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chunkd/-/chunkd-1.0.0.tgz", + "integrity": "sha512-xx3Pb5VF9QaqCotolyZ1ywFBgyuJmu6+9dLiqBxgelEse9Xsr3yUlpoX3O4Oh11M00GT2kYMsRByTKIMJW2Lkg==", + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "ci-parallel-vars": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ci-parallel-vars/-/ci-parallel-vars-1.0.0.tgz", + "integrity": "sha512-u6dx20FBXm+apMi+5x7UVm6EH7BL1gc4XrcnQewjcB7HWRcor/V5qWc3RG2HwpgDJ26gIi2DSEu3B7sXynAw/g==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "clean-yaml-object": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz", + "integrity": "sha1-Y/sRDcLOGoTcIfbZM0h20BCui2g=", + "dev": true + }, + "cli-boxes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", + "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", + "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", + "dev": true + }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "code-excerpt": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-2.1.1.tgz", + "integrity": "sha512-tJLhH3EpFm/1x7heIW0hemXJTUU5EWl2V0EIX558jp05Mt1U6DVryCgkp3l37cxqs+DNbNgxG43SkwJXpQ14Jw==", + "dev": true, + "requires": { + "convert-to-spaces": "^1.0.1" + } + }, + "collapse-white-space": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "optional": true + }, + "common-path-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-1.0.0.tgz", + "integrity": "sha1-zVL28HEuC6q5fW+XModPIvR3UsA=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-md": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/concat-md/-/concat-md-0.3.2.tgz", + "integrity": "sha512-JgCu3AAe0zSFifGjjkKR+zVlABKBA48f6pJcbxNDrIDnPtDTs52KjiFolJ/IVER8XBOsBzkKdvjc0OrYLk28Iw==", + "dev": true, + "requires": { + "doctoc": "^1.4.0", + "front-matter": "^3.0.2", + "globby": "^10.0.1", + "lodash.startcase": "^4.4.0", + "meow": "^5.0.0", + "transform-markdown-links": "^2.0.0" + } + }, + "concordance": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/concordance/-/concordance-4.0.0.tgz", + "integrity": "sha512-l0RFuB8RLfCS0Pt2Id39/oCPykE01pyxgAFypWTlaGRgvLkZrtczZ8atEHpTeEIW+zYWXTBuA9cCSeEOScxReQ==", + "dev": true, + "requires": { + "date-time": "^2.1.0", + "esutils": "^2.0.2", + "fast-diff": "^1.1.2", + "js-string-escape": "^1.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.flattendeep": "^4.4.0", + "lodash.islength": "^4.0.1", + "lodash.merge": "^4.6.1", + "md5-hex": "^2.0.0", + "semver": "^5.5.1", + "well-known-symbols": "^2.0.0" + }, + "dependencies": { + "md5-hex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-2.0.0.tgz", + "integrity": "sha1-0FiOnxx0lUSS7NJKwKxs6ZfZLjM=", + "dev": true, + "requires": { + "md5-o-matic": "^0.1.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "configstore": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-4.0.0.tgz", + "integrity": "sha512-CmquAXFBocrzaSM8mtGPMM/HiWmyIpr4CcJl/rgY2uCObZ/S7cKU0silxslqJejl+t/T9HS8E0PUNQD81JGUEQ==", + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "convert-to-spaces": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz", + "integrity": "sha1-fj5Iu+bZl7FBfdyihoIEtNPYVxU=", + "dev": true + }, + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "date-time": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-time/-/date-time-2.1.0.tgz", + "integrity": "sha512-/9+C44X7lot0IeiyfgJmETtRMhBidBYM2QFFIkGa0U1k+hSyY87Nw7PY3eDqpvCBm7I3WCSfPeZskW/YYq6m4g==", + "dev": true, + "requires": { + "time-zone": "^1.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + } + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "defer-to-connect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.1.tgz", + "integrity": "sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ==", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + } + } + }, + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctoc": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/doctoc/-/doctoc-1.4.0.tgz", + "integrity": "sha512-8IAq3KdMkxhXCUF+xdZxdJxwuz8N2j25sMgqiu4U4JWluN9tRKMlAalxGASszQjlZaBprdD2YfXpL3VPWUD4eg==", + "dev": true, + "requires": { + "@textlint/markdown-to-ast": "~6.0.9", + "anchor-markdown-header": "^0.5.5", + "htmlparser2": "~3.9.2", + "minimist": "~1.2.0", + "underscore": "~1.8.3", + "update-section": "^0.3.0" + }, + "dependencies": { + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + } + } + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "emittery": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.4.1.tgz", + "integrity": "sha512-r4eRSeStEGf6M5SKdrQhhLK5bOwOBxQhIE3YSTnZE3GpKiLfnnhE+tPtrJE79+eDJgm39BM6LSoI8SCx4HbwlQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "empower-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/empower-core/-/empower-core-1.2.0.tgz", + "integrity": "sha512-g6+K6Geyc1o6FdXs9HwrXleCFan7d66G5xSCfSF7x1mJDCes6t0om9lFQG3zOrzh3Bkb/45N0cZ5Gqsf7YrzGQ==", + "dev": true, + "requires": { + "call-signature": "0.0.2", + "core-js": "^2.0.0" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", + "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "equal-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/equal-length/-/equal-length-1.0.1.tgz", + "integrity": "sha1-IcoRLUirJLTh5//A5TOdMf38J0w=", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.0-next.1.tgz", + "integrity": "sha512-7MmGr03N7Rnuid6+wyhD9sHNE2n4tFSwExnU2lQl3lIo2ShXWGePY80zYaoMOmILWv57H0amMjZGHNzzGG70Rw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "dev": true + }, + "espower-location-detector": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/espower-location-detector/-/espower-location-detector-1.0.0.tgz", + "integrity": "sha1-oXt+zFnTDheeK+9z+0E3cEyzMbU=", + "dev": true, + "requires": { + "is-url": "^1.2.1", + "path-is-absolute": "^1.0.0", + "source-map": "^0.5.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "espurify": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/espurify/-/espurify-1.8.1.tgz", + "integrity": "sha512-ZDko6eY/o+D/gHCWyHTU85mKDgYcS4FJj7S+YD6WIInm7GQ6AnOjmcL4+buFV/JOztVLELi/7MmuGU5NHta0Mg==", + "dev": true, + "requires": { + "core-js": "^2.0.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz", + "integrity": "sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2" + } + }, + "fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "dev": true, + "requires": { + "reusify": "^1.0.0" + } + }, + "fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dev": true, + "requires": { + "format": "^0.2.0" + } + }, + "figures": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", + "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=", + "dev": true + }, + "front-matter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-3.1.0.tgz", + "integrity": "sha512-RFEK8N6waWTdwBZOPNEtvwMjZ/hUfpwXkYUYkmmOhQGdhSulXhWrFwiUhdhkduLDiIwbROl/faF1X/PC/GGRMw==", + "dev": true, + "requires": { + "js-yaml": "^3.13.1" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-port": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.0.0.tgz", + "integrity": "sha512-imzMU0FjsZqNa6BqOjbbW6w5BivHIuQKopjpPqcnx0AVHJQKCxK1O+Ab3OrVXhrekqfVMjwA9ZYu062R+KcIsQ==", + "dev": true, + "requires": { + "type-fest": "^0.3.0" + }, + "dependencies": { + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true + } + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "dependencies": { + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true + }, + "hasha": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.1.0.tgz", + "integrity": "sha512-OFPDWmzPN1l7atOV1TgBVmNtBxaIysToK6Ve9DK+vT6pYuklw/nPNT+HJbZi0KDcI6vWB+9tgvZ5YD7fA3CXcA==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + } + }, + "highlight.js": { + "version": "9.18.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz", + "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "dev": true + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "http-cache-semantics": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", + "integrity": "sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==", + "dev": true + }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "irregular-plurals": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz", + "integrity": "sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw==", + "dev": true + }, + "is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true + }, + "is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true + }, + "is-error": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.2.tgz", + "integrity": "sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true + }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + }, + "dependencies": { + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + } + } + }, + "is-npm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-3.0.0.tgz", + "integrity": "sha512-wsigDr1Kkschp2opC4G3yA6r9EgVA6NjRpWzIi9axXqeIaAATPRJc4uLujXe3Nd9uO8KoDyA4MD6aZSeXTADhA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-observable": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-2.0.0.tgz", + "integrity": "sha512-fhBZv3eFKUbyHXZ1oHujdo2tZ+CNbdpdzzlENgCGZUC8keoGxUew2jYFLYcUB4qo7LDD03o4KK11m/QYD7kEjg==", + "dev": true + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "dev": true, + "requires": { + "isobject": "^4.0.0" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-whitespace-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", + "dev": true + }, + "is-word-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", + "dev": true + }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true + }, + "jquery": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz", + "integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==", + "dev": true + }, + "js-string-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", + "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "requires": { + "package-json": "^6.3.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lodash.islength": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.islength/-/lodash.islength-4.0.1.tgz", + "integrity": "sha1-Tpho1FJXXXUK/9NYyXlUPcIO1Xc=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha1-lDbjTtJgk+1/+uGTYUQ1CRXZrdg=", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + }, + "loud-rejection": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-2.2.0.tgz", + "integrity": "sha512-S0FayMXku80toa5sZ6Ro4C+s+EtFDCsyJNG/AzFMfX3AxD5Si4dZsgzm/kKnbOxHl5Cv8jBlno8+3XYIh2pNjQ==", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.2" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "lunr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.8.tgz", + "integrity": "sha512-oxMeX/Y35PNFuZoHp+jUj5OSEmLCaIH4KTFJh7a93cHBoFmpw2IoPs22VIz7vyO2YUnx2Tn9dzIwO2P/4quIRg==", + "dev": true + }, + "make-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", + "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, + "markdown-escapes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", + "dev": true + }, + "marked": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.0.tgz", + "integrity": "sha512-MyUe+T/Pw4TZufHkzAfDj6HarCBWia2y27/bhuYkTaiUnfDYFnCP3KUN+9oM7Wi6JA2rymtVYbQu3spE0GCmxQ==", + "dev": true + }, + "matcher": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-2.1.0.tgz", + "integrity": "sha512-o+nZr+vtJtgPNklyeUKkkH42OsK8WAfdgaJE2FNxcjLPg+5QbeEoT6vRj8Xq/iv18JlQ9cmKsEu0b94ixWf1YQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "md5-hex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", + "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", + "dev": true, + "requires": { + "blueimp-md5": "^2.10.0" + } + }, + "md5-o-matic": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz", + "integrity": "sha1-givM1l4RfFFPqxdrJZRdVBAKA8M=", + "dev": true + }, + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + }, + "dependencies": { + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + } + } + }, + "merge2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + }, + "dependencies": { + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", + "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "observable-to-promise": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/observable-to-promise/-/observable-to-promise-1.0.0.tgz", + "integrity": "sha512-cqnGUrNsE6vdVDTPAX9/WeVzwy/z37vdxupdQXU8vgTXRFH72KCZiZga8aca2ulRPIeem8W3vW9rQHBwfIl2WA==", + "dev": true, + "requires": { + "is-observable": "^2.0.0", + "symbol-observable": "^1.0.4" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "ora": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", + "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-spinners": "^2.0.0", + "log-symbols": "^2.2.0", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + } + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + } + }, + "parse-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", + "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "dev": true, + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picomatch": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz", + "integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-conf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", + "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "load-json-file": "^5.2.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true + } + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "plur": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz", + "integrity": "sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==", + "dev": true, + "requires": { + "irregular-plurals": "^2.0.0" + } + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true + }, + "pretty-ms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-5.1.0.tgz", + "integrity": "sha512-4gaK1skD2gwscCfkswYQRmddUb2GJZtzDGRjHWadVHtK/DIKFufa12MvES6/xu1tVbUYeia5bmLcwJtZJQUqnw==", + "dev": true, + "requires": { + "parse-ms": "^2.1.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", + "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.7" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + }, + "dependencies": { + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + } + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "regexpu-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.1.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, + "registry-auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.0.0.tgz", + "integrity": "sha512-lpQkHxd9UL6tb3k/aHAVfnVtn+Bcs9ob5InuFLLEDqSqeq+AljB8GZW9xY0x7F+xYwEcjKe07nyoxzEYz6yvkw==", + "dev": true, + "requires": { + "rc": "^1.2.8", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "regjsgen": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", + "dev": true + }, + "regjsparser": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.1.tgz", + "integrity": "sha512-7LutE94sz/NKSYegK+/4E77+8DipxF+Qn2Tmu362AcmsF2NYq/wx3+ObvU90TKEhjf7hQoFXo23ajjrXP7eUgg==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "remark-frontmatter": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-1.3.2.tgz", + "integrity": "sha512-2eayxITZ8rezsXdgcXnYB3iLivohm2V/ZT4Ne8uhua6A4pk6GdLE2ZzJnbnINtD1HRLaTdB7RwF9sgUbMptJZA==", + "dev": true, + "requires": { + "fault": "^1.0.1", + "xtend": "^4.0.1" + } + }, + "remark-parse": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", + "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", + "dev": true, + "requires": { + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^1.1.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^1.0.0", + "vfile-location": "^2.0.0", + "xtend": "^4.0.1" + } + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "require-precompiled": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/require-precompiled/-/require-precompiled-0.1.0.tgz", + "integrity": "sha1-WhtS63Dr7UPrmC6XTIWrWVceVvo=", + "dev": true + }, + "resolve": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", + "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "^5.0.3" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shelljs": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.0.tgz", + "integrity": "sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "state-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-bom-buf": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-2.0.0.tgz", + "integrity": "sha512-gLFNHucd6gzb8jMsl5QmZ3QgnUJmp7qn4uUSHNwEXumAp7YizoGYw19ZUVfuq4aBOQUtyn2k8X/CwzWB73W2lQ==", + "dev": true, + "requires": { + "is-utf8": "^0.2.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "structured-source": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-3.0.2.tgz", + "integrity": "sha1-3YAkJeD1PcSm56yjdSkBoczaevU=", + "dev": true, + "requires": { + "boundary": "^1.0.1" + } + }, + "supertap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supertap/-/supertap-1.0.0.tgz", + "integrity": "sha512-HZJ3geIMPgVwKk2VsmO5YHqnnJYl6bV5A9JW2uzqV43WmpgliNEYbuvukfor7URpaqpxuw3CfZ3ONdVbZjCgIA==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "indent-string": "^3.2.0", + "js-yaml": "^3.10.0", + "serialize-error": "^2.1.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "requires": { + "execa": "^0.7.0" + } + }, + "time-zone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", + "integrity": "sha1-mcW/VZWJZq9tBtg73zgA3IL67F0=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "transform-markdown-links": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/transform-markdown-links/-/transform-markdown-links-2.0.0.tgz", + "integrity": "sha1-t56Sg9RHTLmweYma4JFS1OxGMlo=", + "dev": true + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", + "dev": true + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + }, + "trim-off-newlines": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", + "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "trim-trailing-lines": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", + "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==", + "dev": true + }, + "trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "dev": true + }, + "ts-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.2.1.tgz", + "integrity": "sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^1.0.2", + "micromatch": "^4.0.0", + "semver": "^6.0.0" + } + }, + "ts-node": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.4.tgz", + "integrity": "sha512-izbVCRV68EasEPQ8MSIGBNK9dc/4sYJJKYA+IarMQct1RtEot6Xp0bXuClsbUSnKpg50ho+aOAx8en5c+y4OFw==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typedoc": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.17.0.tgz", + "integrity": "sha512-HviZ/SLDPAgJdYqaCor+7rmJnM80Ra2oI0Nj+L/sOIBvh9pY1aCzDtSg3p3n4cgqCmvGu6O90fvMQyjEvlqM+g==", + "dev": true, + "requires": { + "fs-extra": "^8.1.0", + "handlebars": "^4.7.2", + "highlight.js": "^9.17.1", + "lodash": "^4.17.15", + "marked": "^0.8.0", + "minimatch": "^3.0.0", + "progress": "^2.0.3", + "shelljs": "^0.8.3", + "typedoc-default-themes": "^0.8.0" + } + }, + "typedoc-default-themes": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.8.0.tgz", + "integrity": "sha512-0bzAjVEX6ClhE3jLRdU7vR8Fsfbt4ZcPa+gkqyAVgTlQ1fLo/7AkCbTP+hC5XAiByDfRfsAGqj9y6FNjJh0p4A==", + "dev": true, + "requires": { + "backbone": "^1.4.0", + "jquery": "^3.4.1", + "lunr": "^2.3.8", + "underscore": "^1.9.2" + } + }, + "typedoc-plugin-markdown": { + "version": "2.2.17", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-2.2.17.tgz", + "integrity": "sha512-eE6cTeqsZIbjur6RG91Lhx1vTwjR49OHwVPRlmsxY3dthS4FNRL8sHxT5Y9pkosBwv1kSmNGQEPHjMYy1Ag6DQ==", + "dev": true, + "requires": { + "fs-extra": "^8.1.0", + "handlebars": "^4.7.3" + } + }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true + }, + "uglify-js": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz", + "integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.3", + "source-map": "~0.6.1" + } + }, + "uid2": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", + "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=", + "dev": true + }, + "underscore": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz", + "integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ==", + "dev": true + }, + "unherit": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "dev": true, + "requires": { + "inherits": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "dev": true + }, + "unified": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", + "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", + "dev": true, + "requires": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^1.1.0", + "trough": "^1.0.0", + "vfile": "^2.0.0", + "x-is-string": "^0.1.0" + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unique-temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz", + "integrity": "sha1-bc6VsmgcoAPuv7MEpBX5y6vMU4U=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1", + "os-tmpdir": "^1.0.1", + "uid2": "0.0.3" + } + }, + "unist-util-is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", + "dev": true + }, + "unist-util-remove-position": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", + "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", + "dev": true, + "requires": { + "unist-util-visit": "^1.1.0" + } + }, + "unist-util-stringify-position": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", + "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", + "dev": true + }, + "unist-util-visit": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", + "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "dev": true, + "requires": { + "unist-util-visit-parents": "^2.0.0" + } + }, + "unist-util-visit-parents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", + "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "dev": true, + "requires": { + "unist-util-is": "^3.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "update-notifier": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-3.0.1.tgz", + "integrity": "sha512-grrmrB6Zb8DUiyDIaeRTBCkgISYUgETNe7NglEbVsrLWXeESnlCSP50WfRSj/GmzMPl6Uchj24S/p80nP/ZQrQ==", + "dev": true, + "requires": { + "boxen": "^3.0.0", + "chalk": "^2.0.1", + "configstore": "^4.0.0", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.1.0", + "is-npm": "^3.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "update-section": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/update-section/-/update-section-0.3.3.tgz", + "integrity": "sha1-RY8Xgg03gg3GDiC4bZQ5GwASMVg=", + "dev": true + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "requires": { + "prepend-http": "^2.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vfile": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", + "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", + "dev": true, + "requires": { + "is-buffer": "^1.1.4", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^1.0.0", + "vfile-message": "^1.0.0" + } + }, + "vfile-location": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", + "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", + "dev": true + }, + "vfile-message": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", + "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "dev": true, + "requires": { + "unist-util-stringify-position": "^1.1.1" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "well-known-symbols": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", + "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "dev": true, + "requires": { + "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", + "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "x-is-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", + "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", + "dev": true + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/smart-contracts/contract-as/package.json b/smart-contracts/contract-as/package.json new file mode 100644 index 0000000000..521aebf6b6 --- /dev/null +++ b/smart-contracts/contract-as/package.json @@ -0,0 +1,48 @@ +{ + "name": "@casperlabs/contract", + "version": "0.5.0", + "description": "Library for developing CasperLabs smart contracts.", + "main": "index.js", + "ascMain": "assembly/index.ts", + "dependencies": {}, + "devDependencies": { + "@assemblyscript/loader": "^0.9.4", + "assemblyscript": "^0.10.0", + "ava": "^2.4.0", + "concat-md": "^0.3.2", + "ts-loader": "^6.2.1", + "ts-node": "^8.5.4", + "typedoc": "^0.17.0", + "typedoc-plugin-markdown": "^2.2.17", + "typescript": "^3.8.3" + }, + "scripts": { + "test": "npm run asbuild:test && npx ava -v --serial", + "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --debug --use abort=", + "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --optimize --use abort=", + "asbuild:test:bytesrepr": "asc tests/assembly/bytesrepr.spec.as.ts -b build/bytesrepr.spec.as.wasm -t build/bytesrepr.spec.as.wat --sourceMap --optimize", + "asbuild:test:bignum": "asc tests/assembly/bignum.spec.as.ts -b build/bignum.spec.as.wasm -t build/bignum.spec.as.wat --sourceMap --optimize", + "asbuild:test:utils": "asc tests/assembly/utils.spec.as.ts -b build/utils.spec.as.wasm -t build/utils.spec.as.wat --sourceMap --optimize", + "asbuild:test:runtime_args": "asc tests/assembly/runtime_args.spec.as.ts -b build/runtime_args.spec.as.wasm -t build/runtime_args.spec.as.wat --sourceMap --optimize", + "asbuild:test": "npm run asbuild:test:runtime_args && npm run asbuild:test:bytesrepr && npm run asbuild:test:bignum && npm run asbuild:test:utils", + "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized", + "prepublish-docs": "rm -rf apidoc && mkdir apidoc && node_modules/.bin/typedoc assembly/*.ts assembly/collections/*.ts --theme markdown --readme none --ignoreCompilerErrors --hideBreadcrumbs --skipSidebar --excludePrivate --excludeNotExported --out temp-apidoc/ && concat-md --decrease-title-levels --dir-name-as-title temp-apidoc >> README.md", + "prepublishOnly": "cp README.md ._README.md && npm run prepublish-docs", + "postpublish": "rm -rf temp-apidoc && mv ._README.md README.md" + }, + "author": "MichaÅ‚ Papierski ", + "license": "Apache-2.0", + "ava": { + "compileEnhancements": true, + "extensions": [ + "ts" + ], + "require": [ + "ts-node/register", + "ts-node/register/transpile-only" + ], + "files": [ + "tests/**/*.spec.ts" + ] + } +} diff --git a/smart-contracts/contract-as/tests/assembly/bignum.spec.as.ts b/smart-contracts/contract-as/tests/assembly/bignum.spec.as.ts new file mode 100644 index 0000000000..d0cff226fb --- /dev/null +++ b/smart-contracts/contract-as/tests/assembly/bignum.spec.as.ts @@ -0,0 +1,315 @@ +import { hex2bin } from "../utils/helpers"; +import { U512 } from "../../assembly/bignum"; +import { Error } from "../../assembly/bytesrepr"; +import { Pair } from "../../assembly/pair"; +import { checkArraysEqual } from "../../assembly/utils"; +import { arrayToTyped, typedToArray } from "../../assembly/utils"; + +export function testBigNum512Arith(): bool { + let a = U512.fromU64(18446744073709551614); // 2^64-2 + assert(a.toString() == "fffffffffffffffe"); + + let b = U512.fromU64(1); + + assert(b.toString() == "1"); + + a += b; // a==2^64-1 + assert(a.toString() == "ffffffffffffffff"); + + a += b; // a==2^64 + assert(a.toString() == "10000000000000000"); + + a -= b; // a==2^64-1 + assert(a.toString() == "ffffffffffffffff"); + + a -= b; // a==2^64-2 + assert(a.toString() == "fffffffffffffffe"); + + return true; +} + +export function testBigNum512Mul(): bool { + let u64Max = U512.fromU64(18446744073709551615); // 2^64-1 + assert(u64Max.toString() == "ffffffffffffffff"); + + let a = U512.fromU64(18446744073709551615); + + assert(a.toString() == "ffffffffffffffff"); + + a *= u64Max; // (2^64-1) ^ 2 + assert(a.toString() == "fffffffffffffffe0000000000000001"); + + a *= u64Max; // (2^64-1) ^ 3 + assert(a.toString(), "fffffffffffffffd0000000000000002ffffffffffffffff"); + + a *= u64Max; // (2^64-1) ^ 4 + assert(a.toString() == "fffffffffffffffc0000000000000005fffffffffffffffc0000000000000001"); + + a *= u64Max; // (2^64-1) ^ 5 + assert(a.toString() == "fffffffffffffffb0000000000000009fffffffffffffff60000000000000004ffffffffffffffff"); + + a *= u64Max; // (2^64-1) ^ 6 + assert(a.toString() == "fffffffffffffffa000000000000000effffffffffffffec000000000000000efffffffffffffffa0000000000000001"); + a *= u64Max; // (2^64-1) ^ 7 + assert(a.toString() == "fffffffffffffff90000000000000014ffffffffffffffdd0000000000000022ffffffffffffffeb0000000000000006ffffffffffffffff"); + + a *= u64Max; // (2^64-1) ^ 8 + assert(a.toString() == "fffffffffffffff8000000000000001bffffffffffffffc80000000000000045ffffffffffffffc8000000000000001bfffffffffffffff80000000000000001"); + + return true; +} + +export function testBigNumZero(): bool { + let zero = new U512(); + assert(zero.toString() == "0"); + return zero.isZero(); +} + +export function testBigNonZero(): bool { + let nonzero = U512.fromU64(0xffffffff); + assert(nonzero.toString() == "ffffffff"); + return !nonzero.isZero(); +} + +export function testBigNumSetHex(): bool { + let large = U512.fromHex("fffffffffffffff8000000000000001bffffffffffffffc80000000000000045ffffffffffffffc8000000000000001bfffffffffffffff80000000000000001"); + assert(large.toString() == "fffffffffffffff8000000000000001bffffffffffffffc80000000000000045ffffffffffffffc8000000000000001bfffffffffffffff80000000000000001"); + return true; +} + +export function testNeg(): bool { + // big == -big + // in 2s compliment: big == ~(~big+1)+1 + let big = U512.fromHex("e7ed081ae96850db0c7d5b42094b5e09b0631e6b9f63efe4deb90d7dd677c82f8ce52eccda5b03f5190770a763729ae9ab85c76cd1dc9606ec9dcf2e2528fccb"); + assert(big == -(-big)); + return true; +} + +export function testComparison(): bool { + let zero = new U512(); + assert(zero.isZero()); + let one = U512.fromU64(1); + + let u32Max = U512.fromU64(4294967295); + + assert(zero != one); + assert(one == one); + assert(zero == zero); + + assert(zero < one); + assert(zero <= one); + assert(one <= one); + + assert(one > zero); + assert(one >= zero); + assert(one >= one); + + let large1 = U512.fromHex("a25bd58358ae4cd57ba0a4afcde6e9aa55c801d88854541dfc6ea5e3c1fada9ed9cb1e48b0a2d553faa26e5381743415ae1ec593dc67fc525d18e0b6fdf3f7ae"); + let large2 = U512.fromHex("f254bb1c7f6654f5ad104854709cb5c09009ccd2b78b5364fefd3a5fa99381a173c5498966e77d88d443bd1a650b4bcb8bb8a92013a85a7095330bc79a2e22dc"); + + assert(large1.cmp(large2) != 0); + assert(large1 != large2); + assert(large2 == large2); + assert(large1 == large1); + + assert(large1 < large2); + assert(large1 <= large2); + assert(large2 <= large2); + + assert(large2 > large1); + assert(large2 >= large1); + assert(large2 >= large2); + + assert(large1 > zero); + assert(large1 > one); + assert(large1 > u32Max); + assert(large2 > u32Max); + assert(large1 >= u32Max); + assert(u32Max >= one); + assert(one <= u32Max); + assert(one != u32Max); + return true; +} + +export function testBits(): bool { + let zero = new U512(); + assert(zero.bits() == 0); + let one = U512.fromU64(1); + assert(one.bits() == 1); + + let value = new U512(); + for (let i = 0; i < 63; i++) { + value.setU64(1 << i); + assert(value.bits() == i + 1); + } + + let shl512P1 = U512.fromHex("10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + assert(shl512P1.bits() == 509); + + let u512Max = U512.fromHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + assert(u512Max.bits() == 512); + + let mix = U512.fromHex("55555555555"); + assert(mix.bits() == 43); + return true; +} + +export function testDivision(): bool { + let u512Max = U512.fromHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + + let five = U512.fromU64(5); + + let rand = U512.fromHex("6fdf77a12c44899b8456d394e555ac9b62af0b0e70b79c8f8aa3837116c8c2a5"); + + assert(rand.bits() != 0); + let maybeRes1 = u512Max.divMod(rand); + assert(maybeRes1 !== null); + let res1 = >(maybeRes1); + + // result + + assert(res1.first.toString(), "249cebb32c9a2f0d1375ddc28138b727428ed6c66f4ca9f0abeb231cff6df7ec7"); + // remainder + assert(res1.second.toString(), "4d4edcc2e5e0a5416119b88b280018b1b79ffbd0891ae622ee7a6d895e687bbc"); + // recalculate back + assert((res1.first * rand) + res1.second == u512Max); + + // u512max is multiply of 5 + let divided = u512Max / five; + let multiplied = divided * five; + assert(multiplied == u512Max); + + let base10 = ""; + let zero = new U512(); + let ten = U512.fromU64(10); + + assert(five % ten == five); + assert(ten.divMod(zero) === null); + + while (u512Max > zero) { + let maybeRes = u512Max.divMod(ten); + assert(maybeRes !== null); + let res = >(maybeRes); + base10 = res.second.toString() + base10; + u512Max = res.first; + } + assert(base10 == "13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095"); + return true; +} + +export function testSerializeU512Zero(): bool { + let truth = hex2bin("00"); + let result = U512.fromBytes(truth); + assert(result.error == Error.Ok); + assert(result.hasValue()); + let zero = result.value; + assert(zero.isZero()); + const bytes = zero.toBytes(); + return checkArraysEqual(bytes, typedToArray(truth)); +}; + +export function testSerializeU512_3BytesWide(): bool { + let truth = hex2bin("03807801"); + let result = U512.fromBytes(truth); + assert(result.error == Error.Ok); + assert(result.hasValue()); + let num = result.value; + assert(num.toString() == "17880"); // dec: 96384 + const bytes = num.toBytes(); + return checkArraysEqual(bytes, typedToArray(truth)); +}; + +export function testSerializeU512_2BytesWide(): bool { + let truth = hex2bin("020004"); + let result = U512.fromBytes(truth); + assert(result.error == Error.Ok); + assert(result.hasValue()); + let num = result.value; + assert(num.toString() == "400"); // dec: 1024 + const bytes = num.toBytes(); + return checkArraysEqual(bytes, typedToArray(truth)); +}; + +export function testSerializeU512_1BytesWide(): bool { + let truth = hex2bin("0101"); + let result = U512.fromBytes(truth); + assert(result.error == Error.Ok); + assert(result.hasValue()); + let num = result.value; + assert(num.toString() == "1"); + const bytes = num.toBytes(); + return checkArraysEqual(bytes, typedToArray(truth)); +}; + +export function testSerialize100mTimes10(): bool { + let truth = hex2bin("0400ca9a3b"); // bytesrepr truth + + let hex = "3b9aca00"; + + let valU512 = U512.fromHex(hex); + assert(valU512.toString() == hex); + + let bytes = valU512.toBytes(); + assert(bytes !== null) + assert(checkArraysEqual(bytes, typedToArray(truth))); + + let roundTrip = U512.fromBytes(arrayToTyped(bytes)); + assert(roundTrip.error == Error.Ok); + assert(roundTrip.value.toString() == hex); + + return true; +} + +export function testDeserLargeRandomU512(): bool { + // U512::from_dec_str("11047322357349959198658049652287831689404979606512518998046171549088754115972343255984024380249291159341787585633940860990180834807840096331186000119802997").expect("should create"); + let hex = "d2ee2fd630b02f2ffec88918ba0adaba87e76a294af2e5cc9a4710a23da14a6dde1c73780be2815e979547a949e085aa9279db4c3d1d0fde2361cd2e2d392c75"; + + // bytesrepr + let truth = hex2bin("40752c392d2ecd6123de0f1d3d4cdb7992aa85e049a94795975e81e20b78731cde6d4aa13da210479acce5f24a296ae787bada0aba1889c8fe2f2fb030d62feed2"); + + let deser = U512.fromBytes(truth); + assert(deser.error == Error.Ok); + assert(deser !== null); + assert(deser.value.toString() == hex); + + let ser = deser.value.toBytes(); + assert(checkArraysEqual(ser, typedToArray(truth))); + + return true; +} +export function testPrefixOps(): bool { + let a = U512.fromU64(18446744073709551615); // 2^64-2 + assert(a.toString() == "ffffffffffffffff"); + + let one = U512.fromU64(1); + assert(one.toString() == "1"); + + ++a; + assert(a.toString() == "10000000000000000"); + + ++a; + assert(a.toString() == "10000000000000001"); + + --a; + assert(a.toString() == "10000000000000000"); + + --a; + assert(a.toString() == "ffffffffffffffff"); + + let aCloned = a.clone(); + + let aPostInc = a++; + assert(aPostInc == aCloned); + assert(a == aCloned + one); + + let aPostDec = a--; + assert(aPostDec == aCloned); + assert(a == aCloned - one); + return true; +} + +export function testMinMaxValue(): bool { + assert(U512.MAX_VALUE.toString() == "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + assert(U512.MIN_VALUE.toString() == "0"); + return true; +} diff --git a/smart-contracts/contract-as/tests/assembly/bytesrepr.spec.as.ts b/smart-contracts/contract-as/tests/assembly/bytesrepr.spec.as.ts new file mode 100644 index 0000000000..fce768809e --- /dev/null +++ b/smart-contracts/contract-as/tests/assembly/bytesrepr.spec.as.ts @@ -0,0 +1,408 @@ +import { fromBytesU64, toBytesU64, + fromBytesStringList, toBytesStringList, + fromBytesU32, toBytesU32, + fromBytesU8, toBytesU8, + toBytesMap, fromBytesMap, + toBytesPair, + toBytesString, fromBytesString, + toBytesVecT, + Error } from "../../assembly/bytesrepr"; +import { CLValue, CLType, CLTypeTag } from "../../assembly/clvalue"; +import { Key, KeyVariant, AccountHash } from "../../assembly/key"; +import { URef, AccessRights } from "../../assembly/uref"; +import { Option } from "../../assembly/option"; +import { hex2bin } from "../utils/helpers"; +import { checkArraysEqual, checkTypedArrayEqual, checkItemsEqual } from "../../assembly/utils"; +import { typedToArray, arrayToTyped } from "../../assembly/utils"; +import { Pair } from "../../assembly/pair"; +import { EntryPointAccess, PublicAccess, GroupAccess, EntryPoint, EntryPoints, EntryPointType } from "../../assembly"; + +// adding the prefix xtest to one of these functions will cause the test to +// be ignored via the defineTestsFromModule function in spec.tsgit + +export function testDeserializeInvalidU8(): bool { + const bytes: u8[] = []; + let deser = fromBytesU8(arrayToTyped(bytes)); + assert(deser.error == Error.EarlyEndOfStream); + assert(deser.position == 0); + return !deser.hasValue(); +} + +export function testDeSerU8(): bool { + const truth: u8[] = [222]; + let ser = toBytesU8(222); + assert(checkArraysEqual(ser, truth)); + let deser = fromBytesU8(arrayToTyped(ser)); + assert(deser.error == Error.Ok); + return deser.value == 222; +} + +export function xtestDeSerU8_Zero(): bool { + // Used for deserializing Weight (for example) + // NOTE: Currently probably unable to check if `foo(): U8 | null` result is null + const truth: u8[] = [0]; + let ser = toBytesU8(0); + assert(checkArraysEqual(ser, truth)); + let deser = fromBytesU8(arrayToTyped(ser)); + assert(deser.error == Error.Ok); + return deser.value == 0; +} + +export function testDeSerU32(): bool { + const truth: u8[] = [239, 190, 173, 222]; + let ser = toBytesU32(3735928559); + assert(checkArraysEqual(ser, truth)); + let deser = fromBytesU32(arrayToTyped(ser)); + assert(deser.error == Error.Ok); + assert(deser.position == 4); + return deser.value == 0xdeadbeef; +} + +export function testDeSerZeroU32(): bool { + const truth: u8[] = [0, 0, 0, 0]; + let ser = toBytesU32(0); + assert(checkArraysEqual(ser, truth)); + let deser = fromBytesU32(arrayToTyped(ser)); + assert(deser.error == Error.Ok); + assert(deser.hasValue()); + return deser.value == 0; +} + +export function testDeserializeU64_1024(): bool { + const truth = hex2bin("0004000000000000"); + var deser = fromBytesU64(truth); + assert(deser.error == Error.Ok); + assert(deser.position == 8); + return deser.value == 1024; +} + +export function testDeserializeU64_zero(): bool { + const truth = hex2bin("0000000000000000"); + var deser = fromBytesU64(truth); + assert(deser.error == Error.Ok); + assert(deser.position == 8); + assert(deser.hasValue()); + return deser.value == 0; +} + +export function testDeserializeU64_u32max(): bool { + const truth = hex2bin("ffffffff00000000"); + const deser = fromBytesU64(truth); + assert(deser.error == Error.Ok); + assert(deser.position == 8); + return deser.value == 0xffffffff; +} + +export function testDeserializeU64_u32max_plus1(): bool { + const truth = hex2bin("0000000001000000"); + const deser = fromBytesU64(truth); + assert(deser.hasValue()); + assert(deser.error == Error.Ok); + assert(deser.position == 8); + return deser.value == 4294967296; +} + +export function testDeserializeU64_EOF(): bool { + const truth = hex2bin("0000"); + const deser = fromBytesU64(truth); + assert(deser.error == Error.EarlyEndOfStream); + assert(deser.position == 0); + return !deser.hasValue(); +} + +export function testDeserializeU64_u64max(): bool { + const truth = hex2bin("feffffffffffffff"); + const deser = fromBytesU64(truth); + assert(deser.error == Error.Ok); + assert(deser.position == 8); + return deser.value == 18446744073709551614; +} + +export function testDeSerListOfStrings(): bool { + const truth = hex2bin("03000000030000006162630a0000003132333435363738393006000000717765727479"); + const result = fromBytesStringList(truth); + assert(result.error == Error.Ok); + assert(result.hasValue()); + const strList = result.value; + assert(result.position == truth.length); + + assert(checkArraysEqual(strList, [ + "abc", + "1234567890", + "qwerty", + ])); + + let lhs = toBytesStringList(strList); + let rhs = typedToArray(truth); + return checkArraysEqual(lhs, rhs); +}; + +export function testDeSerEmptyListOfStrings(): bool { + const truth = hex2bin("00000000"); + const result = fromBytesStringList(truth); + assert(result.error == Error.Ok); + assert(result.position == 4); + return checkArraysEqual(result.value, []); +}; + +export function testDeSerEmptyMap(): bool { + const truth = hex2bin("00000000"); + const result = fromBytesMap( + truth, + fromBytesString, + Key.fromBytes); + assert(result.error == Error.Ok); + assert(result.hasValue()); + assert(result.position == 4); + return checkArraysEqual(result.value, >>[]); +}; + +export function testSerializeMap(): bool { + // let mut m = BTreeMap::new(); + // m.insert("Key1".to_string(), "Value1".to_string()); + // m.insert("Key2".to_string(), "Value2".to_string()); + // let truth = m.to_bytes().unwrap(); + const truth = hex2bin( + "02000000040000004b6579310600000056616c756531040000004b6579320600000056616c756532" + ); + const pairs = new Array>(); + pairs.push(new Pair("Key1", "Value1")); + pairs.push(new Pair("Key2", "Value2")); + const serialized = toBytesMap(pairs, toBytesString, toBytesString); + assert(checkArraysEqual(serialized, typedToArray(truth))); + + const deser = fromBytesMap( + arrayToTyped(serialized), + fromBytesString, + fromBytesString); + + assert(deser.error == Error.Ok); + assert(deser.position == truth.length); + let listOfPairs = deser.value; + + let res1 = false; + let res2 = false; + for (let i = 0; i < listOfPairs.length; i++) { + if (listOfPairs[i].first == "Key1" && listOfPairs[i].second == "Value1") { + res1 = true; + } + if (listOfPairs[i].first == "Key2" && listOfPairs[i].second == "Value2") { + res2 = true; + } + } + assert(res1); + assert(res2); + return listOfPairs.length == 2; +} + +export function testToBytesVecT(): bool { + // let args = ("get_payment_purse",).parse().unwrap().to_bytes().unwrap(); + const truth = hex2bin("0100000015000000110000006765745f7061796d656e745f70757273650a"); + let serialize = function(item: CLValue): Array { return item.toBytes(); }; + let serialized = toBytesVecT([ + CLValue.fromString("get_payment_purse"), + ], serialize); + return checkArraysEqual(serialized, typedToArray(truth)); +} + +export function testKeyOfURefVariantSerializes(): bool { + // URef with access rights + const truth = hex2bin("022a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a07"); + const urefBytes = hex2bin("2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"); + let uref = new URef(urefBytes, AccessRights.READ_ADD_WRITE); + let key = Key.fromURef(uref); + let serialized = key.toBytes(); + + return checkArraysEqual(serialized, typedToArray(truth)); +}; + +export function testDeSerString(): bool { + // Rust: let bytes = "hello_world".to_bytes().unwrap(); + const truth = hex2bin("0b00000068656c6c6f5f776f726c64"); + + const ser = toBytesString("hello_world"); + assert(checkArraysEqual(ser, typedToArray(truth))); + + const deser = fromBytesString(arrayToTyped(ser)); + assert(deser.error == Error.Ok); + return deser.value == "hello_world"; +} + +export function testDeSerIncompleteString(): bool { + // Rust: let bytes = "hello_world".to_bytes().unwrap(); + const truth = hex2bin("0b00000068656c6c6f5f776f726c"); + // last byte removed from the truth to signalize incomplete data + const deser = fromBytesString(truth); + assert(deser.error == Error.EarlyEndOfStream); + return !deser.hasValue(); +} + +export function testDecodeURefFromBytesWithoutAccessRights(): bool { + const truth = hex2bin("2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a00"); + let urefResult = URef.fromBytes(truth); + assert(urefResult.error == Error.Ok); + assert(urefResult.hasValue()); + let uref = urefResult.value; + + let urefBytes = new Array(32); + urefBytes.fill(42); + + + assert(checkArraysEqual(typedToArray(uref.getBytes()), urefBytes)); + assert(uref.getAccessRights() === AccessRights.NONE); + let serialized = uref.toBytes(); + return checkArraysEqual(serialized, typedToArray(truth)); +} + +export function testDecodeURefFromBytesWithAccessRights(): bool { + const truth = hex2bin("2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a07"); + const urefResult = URef.fromBytes(truth); + assert(urefResult.error == Error.Ok); + assert(urefResult.position == truth.length); + const uref = urefResult.value; + assert(checkArraysEqual(typedToArray(uref.getBytes()), [ + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, + ])); + return uref.getAccessRights() == 0x07; // NOTE: 0x07 is READ_ADD_WRITE +} + +export function testDecodedOptionalIsNone(): bool { + let optionalSome = new Uint8Array(10); + optionalSome[0] = 0; + let res = Option.fromBytes(optionalSome); + assert(res.isNone(), "option should be NONE"); + return !res.isSome(); +}; + +export function testDecodedOptionalIsSome(): bool { + let optionalSome = new Uint8Array(10); + for (let i = 0; i < 10; i++) { + optionalSome[i] = i + 1; + } + let res = Option.fromBytes(optionalSome); + assert(res !== null); + let unwrapped = res.unwrap(); + assert(unwrapped !== null, "unwrapped should not be null"); + let values = unwrapped; + let rhs: Array = [2, 3, 4, 5, 6, 7, 8, 9, 10]; + return checkArraysEqual(typedToArray(values), rhs); +}; + +export function testDeserMapOfNamedKeys(): bool { + + let extraBytes = "fffefd"; + let truthBytes = "0300000001000000410001010101010101010101010101010101010101010101010101010101010101010200000042420202020202020202020202020202020202020202020202020202020202020202020703000000434343010303030303030303030303030303030303030303030303030303030303030303"; + + let truth = hex2bin(truthBytes + extraBytes); + + const mapResult = fromBytesMap( + truth, + fromBytesString, + Key.fromBytes); + assert(mapResult.error == Error.Ok); + let deserializedBytes = mapResult.position; + assert(deserializedBytes == truth.length - hex2bin(extraBytes).length); + + let deser = mapResult.value; + assert(deser.length === 3); + + assert(deser[0].first == "A"); + assert(deser[0].second.variant == KeyVariant.ACCOUNT_ID); + + let accountBytes = new Array(32); + accountBytes.fill(1); + + assert(checkTypedArrayEqual((deser[0].second.account).bytes, arrayToTyped(accountBytes))); + assert(checkTypedArrayEqual((deser[0].second.account).bytes, arrayToTyped(accountBytes))); + + // + + assert(deser[1].first == "BB"); + assert(deser[1].second.variant == KeyVariant.UREF_ID); + + let urefBytes = new Array(32); + urefBytes.fill(2); + + assert(deser[1].second.uref !== null); + let deser1Uref = deser[1].second.uref; + assert(checkTypedArrayEqual(deser1Uref.bytes, arrayToTyped(urefBytes))); + assert(deser1Uref.accessRights == AccessRights.READ_ADD_WRITE); + + // + + assert(deser[2].first == "CCC"); + assert(deser[2].second.variant == KeyVariant.HASH_ID); + + let hashBytes = new Array(32); + hashBytes.fill(3); + + assert(checkTypedArrayEqual(deser[2].second.hash, arrayToTyped(hashBytes))); + + // Compares to truth + + let truthObj = new Array>(); + let keyA = Key.fromAccount(new AccountHash(arrayToTyped(accountBytes))); + truthObj.push(new Pair("A", keyA)); + + let urefB = new URef(arrayToTyped(urefBytes), AccessRights.READ_ADD_WRITE); + let keyB = Key.fromURef(urefB); + truthObj.push(new Pair("BB", keyB)); + + let keyC = Key.fromHash(arrayToTyped(hashBytes)); + truthObj.push(new Pair("CCC", keyC)); + + assert(truthObj.length === deser.length); + assert(truthObj[0] == deser[0]); + assert(truthObj[1] == deser[1]); + assert(truthObj[2] == deser[2]); + + assert(checkArraysEqual(truthObj, deser)); + assert(checkItemsEqual(truthObj, deser)); + + return true; +} + +function useEntryPointAccess(entryPointAccess: EntryPointAccess): Array { + return entryPointAccess.toBytes(); +} + +export function testPublicEntryPointAccess(): bool { + let publicTruth = hex2bin("01"); + let publicAccess = new PublicAccess(); + let bytes = useEntryPointAccess(publicAccess); + assert(bytes.length == 1); + assert(checkArraysEqual(typedToArray(publicTruth), bytes)); + return true; +} + +export function testGroupEntryPointAccess(): bool { + let publicTruth = hex2bin("02030000000700000047726f757020310700000047726f757020320700000047726f75702033"); + let publicAccess = new GroupAccess(["Group 1", "Group 2", "Group 3"]); + let bytes = useEntryPointAccess(publicAccess); + assert(checkArraysEqual(typedToArray(publicTruth), bytes)); + return true; +} + +export function testComplexCLType(): bool { + let type = CLType.fixedList(new CLType(CLTypeTag.U8), 32); + let bytes = type.toBytes(); + let truth = hex2bin("0f0320000000"); + assert(checkArraysEqual(typedToArray(truth), bytes)); + + return true; +} + +export function testToBytesEntryPoint(): bool { + let entryPoints = new EntryPoints(); + let args = new Array>(); + args.push(new Pair("param1", new CLType(CLTypeTag.U512))); + let entryPoint = new EntryPoint("delegate", args, new CLType(CLTypeTag.Unit), new PublicAccess(), EntryPointType.Contract); + entryPoints.addEntryPoint(entryPoint); + let bytes = entryPoints.toBytes(); + let truth = hex2bin("010000000800000064656c65676174650800000064656c65676174650100000006000000706172616d3108090101"); + assert(checkArraysEqual(typedToArray(truth), bytes)); + return true; +} \ No newline at end of file diff --git a/smart-contracts/contract-as/tests/assembly/runtime_args.spec.as.ts b/smart-contracts/contract-as/tests/assembly/runtime_args.spec.as.ts new file mode 100644 index 0000000000..1dcffdb350 --- /dev/null +++ b/smart-contracts/contract-as/tests/assembly/runtime_args.spec.as.ts @@ -0,0 +1,46 @@ +import { hex2bin } from "../utils/helpers"; +import { checkArraysEqual, checkItemsEqual } from "../../assembly/utils"; +import { typedToArray } from "../../assembly/utils"; +import { RuntimeArgs } from "../../assembly/runtime_args"; +import { Pair } from "../../assembly/pair"; + +import { CLValue } from "../../assembly/clvalue"; +import { U512 } from "../../assembly/bignum"; + + +export function testRuntimeArgs(): bool { + // Source: + // + // ``` + // let args = runtime_args! { + // "arg1" => 42u64, + // "arg2" => "Hello, world!", + // "arg3" => U512::from(123456789), + // }; + // ``` + const truth = hex2bin("030000000400000061726731080000002a00000000000000050400000061726732110000000d00000048656c6c6f2c20776f726c64210a0400000061726733050000000415cd5b0708"); + let runtimeArgs = RuntimeArgs.fromArray([ + new Pair("arg1", CLValue.fromU64(42)), + new Pair("arg2", CLValue.fromString("Hello, world!")), + new Pair("arg3", CLValue.fromU512(U512.fromU64(123456789))), + ]); + let bytes = runtimeArgs.toBytes(); + return checkArraysEqual(typedToArray(truth), bytes); +} + +export function testRuntimeArgs_Empty(): bool { + // Source: + // + // ``` + // let args = runtime_args! { + // "arg1" => 42u64, + // "arg2" => "Hello, world!", + // "arg3" => U512::from(123456789), + // }; + // ``` + const truth = hex2bin("00000000"); + + let runtimeArgs = new RuntimeArgs(); + let bytes = runtimeArgs.toBytes(); + return checkArraysEqual(typedToArray(truth), bytes); +} diff --git a/smart-contracts/contract-as/tests/assembly/utils.spec.as.ts b/smart-contracts/contract-as/tests/assembly/utils.spec.as.ts new file mode 100644 index 0000000000..45a066fa75 --- /dev/null +++ b/smart-contracts/contract-as/tests/assembly/utils.spec.as.ts @@ -0,0 +1,68 @@ +import { hex2bin } from "../utils/helpers"; +import { checkArraysEqual, checkItemsEqual } from "../../assembly/utils"; +import { typedToArray } from "../../assembly/utils"; +import { Pair } from "../../assembly/pair"; + +export function testHex2Bin(): bool { + let truth = hex2bin("deadbeef"); + let lhs = typedToArray(truth); + let rhs: Array = [222, 173, 190, 239]; + return checkArraysEqual(lhs, rhs); +} + +export function testcheckArraysEqual(): bool { + assert(checkArraysEqual([], [])); + assert(!checkArraysEqual([1, 2, 3], [1])); + return checkArraysEqual([1, 2, 3], [1, 2, 3]); +} + +export function testItemsEqual(): bool { + let lhs: u32[] = [6,3,5,2,4,1]; + let rhs: u32[] = [1,2,3,4,5,6]; + return checkItemsEqual(lhs, rhs); +} + +export function testItemsNotEqual(): bool { + let lhs1: u32[] = [1,2,3,4,5]; + let rhs1: u32[] = [1,2,3,4,5,6]; + + let lhs2: u32[] = [1,2,3,4,5,6]; + let rhs2: u32[] = [1,2,3,4,5]; + assert(!checkItemsEqual(lhs1, rhs1)); + assert(!checkItemsEqual(rhs2, lhs2)); + return true; +} + +export function testItemsNotEqual2(): bool { + let lhs: u32[] = [1,2,3]; + let rhs: u32[] = [1,3,1]; + assert(!checkItemsEqual(lhs, rhs)); + assert(!checkItemsEqual(rhs, lhs)); + return true; +} + +function comp(lhs: Pair, rhs: Pair): bool { + return lhs.equalsTo(rhs); +} + +export function testPairItemsEqual(): bool { + let lhs: Pair[] = [ + new Pair("Key1", "Value1"), + new Pair("Key2", "Value2"), + new Pair("Key3", "Value3"), + ]; + let rhs: Pair[] = [ + new Pair("Key2", "Value2"), + new Pair("Key3", "Value3"), + new Pair("Key1", "Value1"), + ]; + return checkItemsEqual(lhs, rhs); +} + +export function testEmptyItemsEqual(): bool { + let lhs: Pair[] = [ + ]; + let rhs: Pair[] = [ + ]; + return checkItemsEqual(lhs, rhs); +} diff --git a/smart-contracts/contract-as/tests/bignum.spec.ts b/smart-contracts/contract-as/tests/bignum.spec.ts new file mode 100644 index 0000000000..d3a8e45b76 --- /dev/null +++ b/smart-contracts/contract-as/tests/bignum.spec.ts @@ -0,0 +1,3 @@ +import {defineTestsFromModule} from "./utils/spec"; + +defineTestsFromModule("bignum"); diff --git a/smart-contracts/contract-as/tests/bytesrepr.spec.ts b/smart-contracts/contract-as/tests/bytesrepr.spec.ts new file mode 100644 index 0000000000..cb77da99e3 --- /dev/null +++ b/smart-contracts/contract-as/tests/bytesrepr.spec.ts @@ -0,0 +1,3 @@ +import {defineTestsFromModule} from "./utils/spec"; + +defineTestsFromModule("bytesrepr"); diff --git a/smart-contracts/contract-as/tests/runtime_args.spec.ts b/smart-contracts/contract-as/tests/runtime_args.spec.ts new file mode 100644 index 0000000000..86161ecb60 --- /dev/null +++ b/smart-contracts/contract-as/tests/runtime_args.spec.ts @@ -0,0 +1,3 @@ +import {defineTestsFromModule} from "./utils/spec"; + +defineTestsFromModule("runtime_args"); diff --git a/smart-contracts/contract-as/tests/tsconfig.json b/smart-contracts/contract-as/tests/tsconfig.json new file mode 100644 index 0000000000..72dd2f5d5b --- /dev/null +++ b/smart-contracts/contract-as/tests/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../node_modules/assemblyscript/std/portable.json", + "include": [ + "./**/*.ts", "*.ts", + ] + } + diff --git a/smart-contracts/contract-as/tests/utils.spec.ts b/smart-contracts/contract-as/tests/utils.spec.ts new file mode 100644 index 0000000000..ca54c512aa --- /dev/null +++ b/smart-contracts/contract-as/tests/utils.spec.ts @@ -0,0 +1,3 @@ +import {defineTestsFromModule} from "./utils/spec"; + +defineTestsFromModule("utils"); diff --git a/smart-contracts/contract-as/tests/utils/helpers.ts b/smart-contracts/contract-as/tests/utils/helpers.ts new file mode 100644 index 0000000000..a3a0bf1ca5 --- /dev/null +++ b/smart-contracts/contract-as/tests/utils/helpers.ts @@ -0,0 +1,16 @@ +const HEX_TABLE: String[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + +export function hex2bin(hex: String): Uint8Array { + let bin = new Uint8Array(hex.length / 2); + + for (let i = 0; i < hex.length / 2; i++) { + // NOTE: hex.substr + parseInt gives weird results under AssemblyScript + const hi = HEX_TABLE.indexOf(hex[i * 2]); + assert(hi > -1); + const lo = HEX_TABLE.indexOf(hex[(i * 2) + 1]); + assert(lo > -1); + const number = (hi << 4) | lo; + bin[i] = number; + } + return bin; +} diff --git a/smart-contracts/contract-as/tests/utils/spec.ts b/smart-contracts/contract-as/tests/utils/spec.ts new file mode 100644 index 0000000000..2486a1ef3d --- /dev/null +++ b/smart-contracts/contract-as/tests/utils/spec.ts @@ -0,0 +1,41 @@ +import test from 'ava'; + +const fs = require("fs") +const loader = require("@assemblyscript/loader") + +function loadWasmModule(fileName: String) { + const myImports = { + env: { + abort(msgPtr, filePtr, line, column) { + var msg = msgPtr > 0 ? myModule.__getString(msgPtr) : ""; + var file = myModule.__getString(filePtr); + console.error(`abort called at ${file}:${line}:${column}: ${msg}`); + }, + }, + } + + let myModule = loader.instantiateSync( + fs.readFileSync(__dirname + `/../../build/${fileName}.spec.as.wasm`), + myImports); + + return myModule; +} + +export function defineTestsFromModule(moduleName: string) { + const instance = loadWasmModule(moduleName); + for (const testName in instance) { + const testInstance = instance[testName]; + + const testId = testName.toLowerCase(); + if (testId.startsWith("test")) { + test(testName, t => { + t.truthy(testInstance()); + }); + } + else if (testId.startsWith("xtest")) { + test.skip(testName, t => { + t.truthy(testInstance()); + }); + } + } +} diff --git a/smart-contracts/contract/Cargo.toml b/smart-contracts/contract/Cargo.toml new file mode 100644 index 0000000000..6edc0c5047 --- /dev/null +++ b/smart-contracts/contract/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "casperlabs-contract" +version = "0.6.0" # when updating, also update 'html_root_url' in lib.rs +authors = ["Michael Birch ", "Mateusz Górski "] +edition = "2018" +description = "Library for developing CasperLabs smart contracts." +readme = "README.md" +documentation = "https://docs.rs/casperlabs-contract" +homepage = "https://casperlabs.io" +repository = "https://github.com/CasperLabs/CasperLabs/tree/master/execution-engine/contract" +license-file = "../../LICENSE" + +[features] +default = [] +std = ["casperlabs-types/std"] +test-support = [] +no-unstable-features = ["std", "casperlabs-types/no-unstable-features"] + +[dependencies] +casperlabs-types = { version = "0.6.0", path = "../../types" } +failure = { version = "0.1.6", default-features = false, features = ["failure_derive"] } +hex_fmt = "0.3.0" +wee_alloc = "0.4.5" + +[dev-dependencies] +version-sync = "0.8" + +[package.metadata.docs.rs] +features = ["no-unstable-features"] diff --git a/smart-contracts/contract/README.md b/smart-contracts/contract/README.md new file mode 100644 index 0000000000..2c4eb1468e --- /dev/null +++ b/smart-contracts/contract/README.md @@ -0,0 +1,14 @@ +# `casperlabs-contract` + +[![LOGO](https://raw.githubusercontent.com/CasperLabs/CasperLabs/master/CasperLabs_Logo_Horizontal_RGB.png)](https://casperlabs.io/) + +[![Build Status](https://drone-auto.casperlabs.io/api/badges/CasperLabs/CasperLabs/status.svg?branch=dev)](http://drone-auto.casperlabs.io/CasperLabs/CasperLabs) +[![Crates.io](https://img.shields.io/crates/v/casperlabs-contract)](https://crates.io/crates/casperlabs-contract) +[![Documentation](https://docs.rs/casperlabs-contract/badge.svg)](https://docs.rs/casperlabs-contract) +[![License](https://img.shields.io/badge/license-COSL-blue.svg)](https://github.com/CasperLabs/CasperLabs/blob/master/LICENSE) + +A library for developing CasperLabs smart contracts. + +## License + +Licensed under the [CasperLabs Open Source License (COSL)](https://github.com/CasperLabs/CasperLabs/blob/master/LICENSE). diff --git a/smart-contracts/contract/src/contract_api/account.rs b/smart-contracts/contract/src/contract_api/account.rs new file mode 100644 index 0000000000..7ffe48e654 --- /dev/null +++ b/smart-contracts/contract/src/contract_api/account.rs @@ -0,0 +1,86 @@ +//! Functions for managing accounts. + +use alloc::vec::Vec; +use core::convert::TryFrom; + +use casperlabs_types::{ + account::{ + AccountHash, ActionType, AddKeyFailure, RemoveKeyFailure, SetThresholdFailure, + UpdateKeyFailure, Weight, + }, + bytesrepr, URef, UREF_SERIALIZED_LENGTH, +}; + +use super::to_ptr; +use crate::{contract_api, ext_ffi, unwrap_or_revert::UnwrapOrRevert}; + +/// Retrieves the ID of the account's main purse. +pub fn get_main_purse() -> URef { + let dest_non_null_ptr = contract_api::alloc_bytes(UREF_SERIALIZED_LENGTH); + let bytes = unsafe { + ext_ffi::get_main_purse(dest_non_null_ptr.as_ptr()); + Vec::from_raw_parts( + dest_non_null_ptr.as_ptr(), + UREF_SERIALIZED_LENGTH, + UREF_SERIALIZED_LENGTH, + ) + }; + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + +/// Sets the given [`ActionType`]'s threshold to the provided value. +pub fn set_action_threshold( + action_type: ActionType, + threshold: Weight, +) -> Result<(), SetThresholdFailure> { + let action_type = action_type as u32; + let threshold = threshold.value().into(); + let result = unsafe { ext_ffi::set_action_threshold(action_type, threshold) }; + if result == 0 { + Ok(()) + } else { + Err(SetThresholdFailure::try_from(result).unwrap_or_revert()) + } +} + +/// Adds the given [`AccountHash`] with associated [`Weight`] to the account's associated keys. +pub fn add_associated_key(account_hash: AccountHash, weight: Weight) -> Result<(), AddKeyFailure> { + let (account_hash_ptr, account_hash_size, _bytes) = to_ptr(account_hash); + // Cast of u8 (weight) into i32 is assumed to be always safe + let result = unsafe { + ext_ffi::add_associated_key(account_hash_ptr, account_hash_size, weight.value().into()) + }; + if result == 0 { + Ok(()) + } else { + Err(AddKeyFailure::try_from(result).unwrap_or_revert()) + } +} + +/// Removes the given [`AccountHash`] from the account's associated keys. +pub fn remove_associated_key(account_hash: AccountHash) -> Result<(), RemoveKeyFailure> { + let (account_hash_ptr, account_hash_size, _bytes) = to_ptr(account_hash); + let result = unsafe { ext_ffi::remove_associated_key(account_hash_ptr, account_hash_size) }; + if result == 0 { + Ok(()) + } else { + Err(RemoveKeyFailure::try_from(result).unwrap_or_revert()) + } +} + +/// Updates the [`Weight`] of the given [`AccountHash`] in the account's associated keys. +pub fn update_associated_key( + account_hash: AccountHash, + weight: Weight, +) -> Result<(), UpdateKeyFailure> { + let (account_hash_ptr, account_hash_size, _bytes) = to_ptr(account_hash); + // Cast of u8 (weight) into i32 is assumed to be always safe + let result = unsafe { + ext_ffi::update_associated_key(account_hash_ptr, account_hash_size, weight.value().into()) + }; + if result == 0 { + Ok(()) + } else { + Err(UpdateKeyFailure::try_from(result).unwrap_or_revert()) + } +} diff --git a/smart-contracts/contract/src/contract_api/mod.rs b/smart-contracts/contract/src/contract_api/mod.rs new file mode 100644 index 0000000000..b2e394062e --- /dev/null +++ b/smart-contracts/contract/src/contract_api/mod.rs @@ -0,0 +1,42 @@ +//! Contains support for writing smart contracts. + +pub mod account; +pub mod runtime; +pub mod storage; +pub mod system; + +use alloc::{ + alloc::{alloc, Layout}, + vec::Vec, +}; +use core::{mem, ptr::NonNull}; + +use casperlabs_types::{bytesrepr::ToBytes, ApiError}; + +use crate::unwrap_or_revert::UnwrapOrRevert; + +/// Calculates size and alignment for an array of T. +const fn size_align_for_array(n: usize) -> (usize, usize) { + (n * mem::size_of::(), mem::align_of::()) +} + +/// Allocates bytes +pub fn alloc_bytes(n: usize) -> NonNull { + let (size, align) = size_align_for_array::(n); + // We treat allocated memory as raw bytes, that will be later passed to deserializer which also + // operates on raw bytes. + let layout = Layout::from_size_align(size, align) + .map_err(|_| ApiError::AllocLayout) + .unwrap_or_revert(); + let raw_ptr = unsafe { alloc(layout) }; + NonNull::new(raw_ptr) + .ok_or(ApiError::OutOfMemory) + .unwrap_or_revert() +} + +fn to_ptr(t: T) -> (*const u8, usize, Vec) { + let bytes = t.into_bytes().unwrap_or_revert(); + let ptr = bytes.as_ptr(); + let size = bytes.len(); + (ptr, size, bytes) +} diff --git a/smart-contracts/contract/src/contract_api/runtime.rs b/smart-contracts/contract/src/contract_api/runtime.rs new file mode 100644 index 0000000000..466696210e --- /dev/null +++ b/smart-contracts/contract/src/contract_api/runtime.rs @@ -0,0 +1,336 @@ +//! Functions for interacting with the current runtime. + +// Can be removed once https://github.com/rust-lang/rustfmt/issues/3362 is resolved. +#[rustfmt::skip] +use alloc::vec; +use alloc::vec::Vec; +use core::mem::MaybeUninit; + +use casperlabs_types::{ + account::AccountHash, + api_error, + bytesrepr::{self, FromBytes}, + contracts::{ContractVersion, NamedKeys}, + ApiError, BlockTime, CLTyped, CLValue, ContractHash, ContractPackageHash, Key, Phase, + RuntimeArgs, URef, BLOCKTIME_SERIALIZED_LENGTH, PHASE_SERIALIZED_LENGTH, +}; + +use crate::{contract_api, ext_ffi, unwrap_or_revert::UnwrapOrRevert}; + +/// Returns the given [`CLValue`] to the host, terminating the currently running module. +/// +/// Note this function is only relevant to contracts stored on chain which are invoked via +/// [`call_contract`] and can thus return a value to their caller. The return value of a directly +/// deployed contract is never used. +pub fn ret(value: CLValue) -> ! { + let (ptr, size, _bytes) = contract_api::to_ptr(value); + unsafe { + ext_ffi::ret(ptr, size); + } +} + +/// Stops execution of a contract and reverts execution effects with a given [`ApiError`]. +/// +/// The provided `ApiError` is returned in the form of a numeric exit code to the caller via the +/// deploy response. +pub fn revert>(error: T) -> ! { + unsafe { + ext_ffi::revert(error.into().into()); + } +} + +/// Calls the given stored contract, passing the given arguments to it. +/// +/// If the stored contract calls [`ret`], then that value is returned from `call_contract`. If the +/// stored contract calls [`revert`], then execution stops and `call_contract` doesn't return. +/// Otherwise `call_contract` returns `()`. +pub fn call_contract( + contract_hash: ContractHash, + entry_point_name: &str, + runtime_args: RuntimeArgs, +) -> T { + let (contract_hash_ptr, contract_hash_size, _bytes1) = contract_api::to_ptr(contract_hash); + let (entry_point_name_ptr, entry_point_name_size, _bytes2) = + contract_api::to_ptr(entry_point_name); + let (runtime_args_ptr, runtime_args_size, _bytes2) = contract_api::to_ptr(runtime_args); + + let bytes_written = { + let mut bytes_written = MaybeUninit::uninit(); + let ret = unsafe { + ext_ffi::call_contract( + contract_hash_ptr, + contract_hash_size, + entry_point_name_ptr, + entry_point_name_size, + runtime_args_ptr, + runtime_args_size, + bytes_written.as_mut_ptr(), + ) + }; + api_error::result_from(ret).unwrap_or_revert(); + unsafe { bytes_written.assume_init() } + }; + deserialize_contract_result(bytes_written) +} + +/// Invokes the specified `entry_point_name` of stored logic at a specific `contract_package_hash` +/// address, for the most current version of a contract package by default or a specific +/// `contract_version` if one is provided, and passing the provided `runtime_args` to it +/// +/// If the stored contract calls [`ret`], then that value is returned from +/// `call_versioned_contract`. If the stored contract calls [`revert`], then execution stops and +/// `call_versioned_contract` doesn't return. Otherwise `call_versioned_contract` returns `()`. +pub fn call_versioned_contract( + contract_package_hash: ContractPackageHash, + contract_version: Option, + entry_point_name: &str, + runtime_args: RuntimeArgs, +) -> T { + let (contract_package_hash_ptr, contract_package_hash_size, _bytes) = + contract_api::to_ptr(contract_package_hash); + let (contract_version_ptr, contract_version_size, _bytes) = + contract_api::to_ptr(contract_version); + let (entry_point_name_ptr, entry_point_name_size, _bytes) = + contract_api::to_ptr(entry_point_name); + let (runtime_args_ptr, runtime_args_size, _bytes) = contract_api::to_ptr(runtime_args); + + let bytes_written = { + let mut bytes_written = MaybeUninit::uninit(); + let ret = unsafe { + ext_ffi::call_versioned_contract( + contract_package_hash_ptr, + contract_package_hash_size, + contract_version_ptr, + contract_version_size, + entry_point_name_ptr, + entry_point_name_size, + runtime_args_ptr, + runtime_args_size, + bytes_written.as_mut_ptr(), + ) + }; + api_error::result_from(ret).unwrap_or_revert(); + unsafe { bytes_written.assume_init() } + }; + deserialize_contract_result(bytes_written) +} + +fn deserialize_contract_result(bytes_written: usize) -> T { + let serialized_result = if bytes_written == 0 { + // If no bytes were written, the host buffer hasn't been set and hence shouldn't be read. + vec![] + } else { + // NOTE: this is a copy of the contents of `read_host_buffer()`. Calling that directly from + // here causes several contracts to fail with a Wasmi `Unreachable` error. + let bytes_non_null_ptr = contract_api::alloc_bytes(bytes_written); + let mut dest: Vec = unsafe { + Vec::from_raw_parts(bytes_non_null_ptr.as_ptr(), bytes_written, bytes_written) + }; + read_host_buffer_into(&mut dest).unwrap_or_revert(); + dest + }; + + bytesrepr::deserialize(serialized_result).unwrap_or_revert() +} + +fn get_named_arg_size(name: &str) -> Option { + let mut arg_size: usize = 0; + let ret = unsafe { + ext_ffi::get_named_arg_size( + name.as_bytes().as_ptr(), + name.len(), + &mut arg_size as *mut usize, + ) + }; + match api_error::result_from(ret) { + Ok(_) => Some(arg_size), + Err(ApiError::MissingArgument) => None, + Err(e) => revert(e), + } +} + +/// Returns given named argument passed to the host for the current module invocation. +/// +/// Note that this is only relevant to contracts stored on-chain since a contract deployed directly +/// is not invoked with any arguments. +pub fn get_named_arg(name: &str) -> T { + let arg_size = get_named_arg_size(name).unwrap_or_revert_with(ApiError::MissingArgument); + let arg_bytes = if arg_size > 0 { + let res = { + let data_non_null_ptr = contract_api::alloc_bytes(arg_size); + let ret = unsafe { + ext_ffi::get_named_arg( + name.as_bytes().as_ptr(), + name.len(), + data_non_null_ptr.as_ptr(), + arg_size, + ) + }; + let data = + unsafe { Vec::from_raw_parts(data_non_null_ptr.as_ptr(), arg_size, arg_size) }; + api_error::result_from(ret).map(|_| data) + }; + // Assumed to be safe as `get_named_arg_size` checks the argument already + res.unwrap_or_revert() + } else { + // Avoids allocation with 0 bytes and a call to get_named_arg + Vec::new() + }; + bytesrepr::deserialize(arg_bytes).unwrap_or_revert_with(ApiError::InvalidArgument) +} + +/// Returns the caller of the current context, i.e. the [`AccountHash`] of the account which made +/// the deploy request. +pub fn get_caller() -> AccountHash { + let output_size = { + let mut output_size = MaybeUninit::uninit(); + let ret = unsafe { ext_ffi::get_caller(output_size.as_mut_ptr()) }; + api_error::result_from(ret).unwrap_or_revert(); + unsafe { output_size.assume_init() } + }; + let buf = read_host_buffer(output_size).unwrap_or_revert(); + bytesrepr::deserialize(buf).unwrap_or_revert() +} + +/// Returns the current [`BlockTime`]. +pub fn get_blocktime() -> BlockTime { + let dest_non_null_ptr = contract_api::alloc_bytes(BLOCKTIME_SERIALIZED_LENGTH); + let bytes = unsafe { + ext_ffi::get_blocktime(dest_non_null_ptr.as_ptr()); + Vec::from_raw_parts( + dest_non_null_ptr.as_ptr(), + BLOCKTIME_SERIALIZED_LENGTH, + BLOCKTIME_SERIALIZED_LENGTH, + ) + }; + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + +/// Returns the current [`Phase`]. +pub fn get_phase() -> Phase { + let dest_non_null_ptr = contract_api::alloc_bytes(PHASE_SERIALIZED_LENGTH); + unsafe { ext_ffi::get_phase(dest_non_null_ptr.as_ptr()) }; + let bytes = unsafe { + Vec::from_raw_parts( + dest_non_null_ptr.as_ptr(), + PHASE_SERIALIZED_LENGTH, + PHASE_SERIALIZED_LENGTH, + ) + }; + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + +/// Returns the requested named [`Key`] from the current context. +/// +/// The current context is either the caller's account or a stored contract depending on whether the +/// currently-executing module is a direct call or a sub-call respectively. +pub fn get_key(name: &str) -> Option { + let (name_ptr, name_size, _bytes) = contract_api::to_ptr(name); + let mut key_bytes = vec![0u8; Key::max_serialized_length()]; + let mut total_bytes: usize = 0; + let ret = unsafe { + ext_ffi::get_key( + name_ptr, + name_size, + key_bytes.as_mut_ptr(), + key_bytes.len(), + &mut total_bytes as *mut usize, + ) + }; + match api_error::result_from(ret) { + Ok(_) => {} + Err(ApiError::MissingKey) => return None, + Err(e) => revert(e), + } + key_bytes.truncate(total_bytes); + let key: Key = bytesrepr::deserialize(key_bytes).unwrap_or_revert(); + Some(key) +} + +/// Returns `true` if `name` exists in the current context's named keys. +/// +/// The current context is either the caller's account or a stored contract depending on whether the +/// currently-executing module is a direct call or a sub-call respectively. +pub fn has_key(name: &str) -> bool { + let (name_ptr, name_size, _bytes) = contract_api::to_ptr(name); + let result = unsafe { ext_ffi::has_key(name_ptr, name_size) }; + result == 0 +} + +/// Stores the given [`Key`] under `name` in the current context's named keys. +/// +/// The current context is either the caller's account or a stored contract depending on whether the +/// currently-executing module is a direct call or a sub-call respectively. +pub fn put_key(name: &str, key: Key) { + let (name_ptr, name_size, _bytes) = contract_api::to_ptr(name); + let (key_ptr, key_size, _bytes2) = contract_api::to_ptr(key); + unsafe { ext_ffi::put_key(name_ptr, name_size, key_ptr, key_size) }; +} + +/// Removes the [`Key`] stored under `name` in the current context's named keys. +/// +/// The current context is either the caller's account or a stored contract depending on whether the +/// currently-executing module is a direct call or a sub-call respectively. +pub fn remove_key(name: &str) { + let (name_ptr, name_size, _bytes) = contract_api::to_ptr(name); + unsafe { ext_ffi::remove_key(name_ptr, name_size) } +} + +/// Returns the named keys of the current context. +/// +/// The current context is either the caller's account or a stored contract depending on whether the +/// currently-executing module is a direct call or a sub-call respectively. +pub fn list_named_keys() -> NamedKeys { + let (total_keys, result_size) = { + let mut total_keys = MaybeUninit::uninit(); + let mut result_size = 0; + let ret = unsafe { + ext_ffi::load_named_keys(total_keys.as_mut_ptr(), &mut result_size as *mut usize) + }; + api_error::result_from(ret).unwrap_or_revert(); + let total_keys = unsafe { total_keys.assume_init() }; + (total_keys, result_size) + }; + if total_keys == 0 { + return NamedKeys::new(); + } + let bytes = read_host_buffer(result_size).unwrap_or_revert(); + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + +/// Validates uref against named keys. +pub fn is_valid_uref(uref: URef) -> bool { + let (uref_ptr, uref_size, _bytes) = contract_api::to_ptr(uref); + let result = unsafe { ext_ffi::is_valid_uref(uref_ptr, uref_size) }; + result != 0 +} + +fn read_host_buffer_into(dest: &mut [u8]) -> Result { + let mut bytes_written = MaybeUninit::uninit(); + let ret = unsafe { + ext_ffi::read_host_buffer(dest.as_mut_ptr(), dest.len(), bytes_written.as_mut_ptr()) + }; + // NOTE: When rewriting below expression as `result_from(ret).map(|_| unsafe { ... })`, and the + // caller ignores the return value, execution of the contract becomes unstable and ultimately + // leads to `Unreachable` error. + api_error::result_from(ret)?; + Ok(unsafe { bytes_written.assume_init() }) +} + +pub(crate) fn read_host_buffer(size: usize) -> Result, ApiError> { + let mut dest: Vec = if size == 0 { + Vec::new() + } else { + let bytes_non_null_ptr = contract_api::alloc_bytes(size); + unsafe { Vec::from_raw_parts(bytes_non_null_ptr.as_ptr(), size, size) } + }; + read_host_buffer_into(&mut dest)?; + Ok(dest) +} + +#[cfg(feature = "test-support")] +/// Prints a debug message +pub fn print(text: &str) { + let (text_ptr, text_size, _bytes) = contract_api::to_ptr(text); + unsafe { ext_ffi::print(text_ptr, text_size) } +} diff --git a/smart-contracts/contract/src/contract_api/storage.rs b/smart-contracts/contract/src/contract_api/storage.rs new file mode 100644 index 0000000000..8c5ceb63ad --- /dev/null +++ b/smart-contracts/contract/src/contract_api/storage.rs @@ -0,0 +1,349 @@ +//! Functions for accessing and mutating local and global state. + +use alloc::{collections::BTreeSet, string::String, vec, vec::Vec}; +use core::{convert::From, mem::MaybeUninit}; + +use casperlabs_types::{ + api_error, + bytesrepr::{self, FromBytes, ToBytes}, + contracts::{ContractVersion, EntryPoints, NamedKeys}, + AccessRights, ApiError, CLTyped, CLValue, ContractHash, ContractPackageHash, Key, URef, + UREF_SERIALIZED_LENGTH, +}; + +use crate::{ + contract_api::{self, runtime, runtime::revert}, + ext_ffi, + unwrap_or_revert::UnwrapOrRevert, +}; + +/// Reads value under `uref` in the global state. +pub fn read(uref: URef) -> Result, bytesrepr::Error> { + let key: Key = uref.into(); + let (key_ptr, key_size, _bytes) = contract_api::to_ptr(key); + + let value_size = { + let mut value_size = MaybeUninit::uninit(); + let ret = unsafe { ext_ffi::read_value(key_ptr, key_size, value_size.as_mut_ptr()) }; + match api_error::result_from(ret) { + Ok(_) => unsafe { value_size.assume_init() }, + Err(ApiError::ValueNotFound) => return Ok(None), + Err(e) => runtime::revert(e), + } + }; + + let value_bytes = runtime::read_host_buffer(value_size).unwrap_or_revert(); + Ok(Some(bytesrepr::deserialize(value_bytes)?)) +} + +/// Reads value under `uref` in the global state, reverts if value not found or is not `T`. +pub fn read_or_revert(uref: URef) -> T { + read(uref) + .unwrap_or_revert_with(ApiError::Read) + .unwrap_or_revert_with(ApiError::ValueNotFound) +} + +/// Reads the value under `key` in the context-local partition of global state. +pub fn read_local( + key: &K, +) -> Result, bytesrepr::Error> { + let key_bytes = key.to_bytes()?; + + let value_size = { + let mut value_size = MaybeUninit::uninit(); + let ret = unsafe { + ext_ffi::read_value_local(key_bytes.as_ptr(), key_bytes.len(), value_size.as_mut_ptr()) + }; + match api_error::result_from(ret) { + Ok(_) => unsafe { value_size.assume_init() }, + Err(ApiError::ValueNotFound) => return Ok(None), + Err(e) => runtime::revert(e), + } + }; + + let value_bytes = runtime::read_host_buffer(value_size).unwrap_or_revert(); + Ok(Some(bytesrepr::deserialize(value_bytes)?)) +} + +/// Writes `value` under `uref` in the global state. +pub fn write(uref: URef, value: T) { + let key = Key::from(uref); + let (key_ptr, key_size, _bytes1) = contract_api::to_ptr(key); + + let cl_value = CLValue::from_t(value).unwrap_or_revert(); + let (cl_value_ptr, cl_value_size, _bytes2) = contract_api::to_ptr(cl_value); + + unsafe { + ext_ffi::write(key_ptr, key_size, cl_value_ptr, cl_value_size); + } +} + +/// Writes `value` under `key` in the context-local partition of global state. +pub fn write_local(key: K, value: V) { + let (key_ptr, key_size, _bytes1) = contract_api::to_ptr(key); + + let cl_value = CLValue::from_t(value).unwrap_or_revert(); + let (cl_value_ptr, cl_value_size, _bytes) = contract_api::to_ptr(cl_value); + + unsafe { + ext_ffi::write_local(key_ptr, key_size, cl_value_ptr, cl_value_size); + } +} + +/// Adds `value` to the one currently under `uref` in the global state. +pub fn add(uref: URef, value: T) { + let key = Key::from(uref); + let (key_ptr, key_size, _bytes1) = contract_api::to_ptr(key); + + let cl_value = CLValue::from_t(value).unwrap_or_revert(); + let (cl_value_ptr, cl_value_size, _bytes2) = contract_api::to_ptr(cl_value); + + unsafe { + // Could panic if `value` cannot be added to the given value in memory. + ext_ffi::add(key_ptr, key_size, cl_value_ptr, cl_value_size); + } +} + +/// Adds `value` to the one currently under `key` in the context-local partition of global state. +pub fn add_local(key: K, value: V) { + let (key_ptr, key_size, _bytes1) = contract_api::to_ptr(key); + + let cl_value = CLValue::from_t(value).unwrap_or_revert(); + let (cl_value_ptr, cl_value_size, _bytes) = contract_api::to_ptr(cl_value); + + unsafe { + ext_ffi::add_local(key_ptr, key_size, cl_value_ptr, cl_value_size); + } +} + +/// Returns a new unforgeable pointer, where the value is initialized to `init`. +pub fn new_uref(init: T) -> URef { + let uref_non_null_ptr = contract_api::alloc_bytes(UREF_SERIALIZED_LENGTH); + let cl_value = CLValue::from_t(init).unwrap_or_revert(); + let (cl_value_ptr, cl_value_size, _cl_value_bytes) = contract_api::to_ptr(cl_value); + let bytes = unsafe { + ext_ffi::new_uref(uref_non_null_ptr.as_ptr(), cl_value_ptr, cl_value_size); // URef has `READ_ADD_WRITE` + Vec::from_raw_parts( + uref_non_null_ptr.as_ptr(), + UREF_SERIALIZED_LENGTH, + UREF_SERIALIZED_LENGTH, + ) + }; + bytesrepr::deserialize(bytes).unwrap_or_revert() +} + +/// Create a new contract stored under a Key::Hash at version 1 +/// if `named_keys` are provided, will apply them +/// if `hash_name` is provided, puts contract hash in current context's named keys under `hash_name` +/// if `uref_name` is provided, puts access_uref in current context's named keys under `uref_name` +pub fn new_contract( + entry_points: EntryPoints, + named_keys: Option, + hash_name: Option, + uref_name: Option, +) -> (ContractHash, ContractVersion) { + let (contract_package_hash, access_uref) = create_contract_package_at_hash(); + + if let Some(hash_name) = hash_name { + runtime::put_key(&hash_name, contract_package_hash.into()); + }; + + if let Some(uref_name) = uref_name { + runtime::put_key(&uref_name, access_uref.into()); + }; + + let named_keys = match named_keys { + Some(named_keys) => named_keys, + None => NamedKeys::new(), + }; + + add_contract_version(contract_package_hash, entry_points, named_keys) +} + +/// Create a new (versioned) contract stored under a Key::Hash. Initially there +/// are no versions; a version must be added via `add_contract_version` before +/// the contract can be executed. +pub fn create_contract_package_at_hash() -> (ContractPackageHash, URef) { + let mut hash_addr = ContractPackageHash::default(); + let mut access_addr = [0u8; 32]; + unsafe { + ext_ffi::create_contract_package_at_hash(hash_addr.as_mut_ptr(), access_addr.as_mut_ptr()); + } + let contract_package_hash = hash_addr; + let access_uref = URef::new(access_addr, AccessRights::READ_ADD_WRITE); + + (contract_package_hash, access_uref) +} + +/// Create a new "user group" for a (versioned) contract. User groups associate +/// a set of URefs with a label. Entry points on a contract can be given a list of +/// labels they accept and the runtime will check that a URef from at least one +/// of the allowed groups is present in the caller's context before +/// execution. This allows access control for entry_points of a contract. This +/// function returns the list of new URefs created for the group (the list will +/// contain `num_new_urefs` elements). +pub fn create_contract_user_group( + contract_package_hash: ContractPackageHash, + group_label: &str, + num_new_urefs: u8, // number of new urefs to populate the group with + existing_urefs: BTreeSet, // also include these existing urefs in the group +) -> Result, ApiError> { + let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) = + contract_api::to_ptr(contract_package_hash); + let (label_ptr, label_size, _bytes3) = contract_api::to_ptr(group_label); + let (existing_urefs_ptr, existing_urefs_size, _bytes4) = contract_api::to_ptr(existing_urefs); + + let value_size = { + let mut output_size = MaybeUninit::uninit(); + let ret = unsafe { + ext_ffi::create_contract_user_group( + contract_package_hash_ptr, + contract_package_hash_size, + label_ptr, + label_size, + num_new_urefs, + existing_urefs_ptr, + existing_urefs_size, + output_size.as_mut_ptr(), + ) + }; + api_error::result_from(ret).unwrap_or_revert(); + unsafe { output_size.assume_init() } + }; + + let value_bytes = runtime::read_host_buffer(value_size).unwrap_or_revert(); + Ok(bytesrepr::deserialize(value_bytes).unwrap_or_revert()) +} + +/// Extends specified group with a new `URef`. +pub fn provision_contract_user_group_uref( + package_hash: ContractPackageHash, + label: &str, +) -> Result { + let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) = + contract_api::to_ptr(package_hash); + let (label_ptr, label_size, _bytes2) = contract_api::to_ptr(label); + let value_size = { + let mut value_size = MaybeUninit::uninit(); + let ret = unsafe { + ext_ffi::provision_contract_user_group_uref( + contract_package_hash_ptr, + contract_package_hash_size, + label_ptr, + label_size, + value_size.as_mut_ptr(), + ) + }; + api_error::result_from(ret)?; + unsafe { value_size.assume_init() } + }; + let value_bytes = runtime::read_host_buffer(value_size).unwrap_or_revert(); + Ok(bytesrepr::deserialize(value_bytes).unwrap_or_revert()) +} + +/// Removes specified urefs from a named group. +pub fn remove_contract_user_group_urefs( + package_hash: ContractPackageHash, + label: &str, + urefs: BTreeSet, +) -> Result<(), ApiError> { + let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) = + contract_api::to_ptr(package_hash); + let (label_ptr, label_size, _bytes3) = contract_api::to_ptr(label); + let (urefs_ptr, urefs_size, _bytes4) = contract_api::to_ptr(urefs); + let ret = unsafe { + ext_ffi::remove_contract_user_group_urefs( + contract_package_hash_ptr, + contract_package_hash_size, + label_ptr, + label_size, + urefs_ptr, + urefs_size, + ) + }; + api_error::result_from(ret) +} + +/// Remove a named group from given contract. +pub fn remove_contract_user_group( + package_hash: ContractPackageHash, + label: &str, +) -> Result<(), ApiError> { + let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) = + contract_api::to_ptr(package_hash); + let (label_ptr, label_size, _bytes3) = contract_api::to_ptr(label); + let ret = unsafe { + ext_ffi::remove_contract_user_group( + contract_package_hash_ptr, + contract_package_hash_size, + label_ptr, + label_size, + ) + }; + api_error::result_from(ret) +} + +/// Add a new version of a contract to the contract stored at the given +/// `Key`. Note that this contract must have been created by +/// `create_contract` or `create_contract_package_at_hash` first. +pub fn add_contract_version( + contract_package_hash: ContractPackageHash, + entry_points: EntryPoints, + named_keys: NamedKeys, +) -> (ContractHash, ContractVersion) { + let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) = + contract_api::to_ptr(contract_package_hash); + let (entry_points_ptr, entry_points_size, _bytes4) = contract_api::to_ptr(entry_points); + let (named_keys_ptr, named_keys_size, _bytes5) = contract_api::to_ptr(named_keys); + + let mut output_ptr = vec![0u8; Key::max_serialized_length()]; + let mut total_bytes: usize = 0; + + let mut contract_version: ContractVersion = 0; + + let ret = unsafe { + ext_ffi::add_contract_version( + contract_package_hash_ptr, + contract_package_hash_size, + &mut contract_version as *mut ContractVersion, + entry_points_ptr, + entry_points_size, + named_keys_ptr, + named_keys_size, + output_ptr.as_mut_ptr(), + output_ptr.len(), + &mut total_bytes as *mut usize, + ) + }; + match api_error::result_from(ret) { + Ok(_) => {} + Err(e) => revert(e), + } + output_ptr.truncate(total_bytes); + let contract_hash = bytesrepr::deserialize(output_ptr).unwrap_or_revert(); + (contract_hash, contract_version) +} + +/// Disable a version of a contract from the contract stored at the given +/// `Key`. That version of the contract will no longer be callable by +/// `call_versioned_contract`. Note that this contract must have been created by +/// `create_contract` or `create_contract_package_at_hash` first. +pub fn disable_contract_version( + contract_package_hash: ContractPackageHash, + contract_hash: ContractHash, +) -> Result<(), ApiError> { + let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) = + contract_api::to_ptr(contract_package_hash); + let (contract_hash_ptr, contract_hash_size, _bytes2) = contract_api::to_ptr(contract_hash); + + let result = unsafe { + ext_ffi::disable_contract_version( + contract_package_hash_ptr, + contract_package_hash_size, + contract_hash_ptr, + contract_hash_size, + ) + }; + + api_error::result_from(result) +} diff --git a/smart-contracts/contract/src/contract_api/system.rs b/smart-contracts/contract/src/contract_api/system.rs new file mode 100644 index 0000000000..4a70c7417c --- /dev/null +++ b/smart-contracts/contract/src/contract_api/system.rs @@ -0,0 +1,154 @@ +//! Functions for interacting with the system contracts. + +use alloc::vec::Vec; +use core::mem::MaybeUninit; + +use casperlabs_types::{ + account::AccountHash, api_error, bytesrepr, ApiError, ContractHash, SystemContractType, + TransferResult, TransferredTo, URef, U512, UREF_SERIALIZED_LENGTH, +}; + +use crate::{ + contract_api::{self, runtime}, + ext_ffi, + unwrap_or_revert::UnwrapOrRevert, +}; + +fn get_system_contract(system_contract: SystemContractType) -> ContractHash { + let system_contract_index = system_contract.into(); + let contract_hash: ContractHash = { + let result = { + let mut hash_data_raw = ContractHash::default(); + let value = unsafe { + ext_ffi::get_system_contract( + system_contract_index, + hash_data_raw.as_mut_ptr(), + hash_data_raw.len(), + ) + }; + api_error::result_from(value).map(|_| hash_data_raw) + }; + // Revert for any possible error that happened on host side + let contract_hash_bytes = result.unwrap_or_else(|e| runtime::revert(e)); + // Deserializes a valid URef passed from the host side + bytesrepr::deserialize(contract_hash_bytes.to_vec()).unwrap_or_revert() + }; + contract_hash +} + +/// Returns a read-only pointer to the Mint contract. +/// +/// Any failure will trigger [`revert`](runtime::revert) with an appropriate [`ApiError`]. +pub fn get_mint() -> ContractHash { + get_system_contract(SystemContractType::Mint) +} + +/// Returns a read-only pointer to the Proof of Stake contract. +/// +/// Any failure will trigger [`revert`](runtime::revert) with an appropriate [`ApiError`]. +pub fn get_proof_of_stake() -> ContractHash { + get_system_contract(SystemContractType::ProofOfStake) +} + +/// Returns a read-only pointer to the Standard Payment contract. +/// +/// Any failure will trigger [`revert`](runtime::revert) with an appropriate [`ApiError`]. +pub fn get_standard_payment() -> ContractHash { + get_system_contract(SystemContractType::StandardPayment) +} + +/// Creates a new empty purse and returns its [`URef`]. +pub fn create_purse() -> URef { + let purse_non_null_ptr = contract_api::alloc_bytes(UREF_SERIALIZED_LENGTH); + unsafe { + let ret = ext_ffi::create_purse(purse_non_null_ptr.as_ptr(), UREF_SERIALIZED_LENGTH); + if ret == 0 { + let bytes = Vec::from_raw_parts( + purse_non_null_ptr.as_ptr(), + UREF_SERIALIZED_LENGTH, + UREF_SERIALIZED_LENGTH, + ); + bytesrepr::deserialize(bytes).unwrap_or_revert() + } else { + runtime::revert(ApiError::PurseNotCreated) + } + } +} + +/// Returns the balance in motes of the given purse. +pub fn get_balance(purse: URef) -> Option { + let (purse_ptr, purse_size, _bytes) = contract_api::to_ptr(purse); + + let value_size = { + let mut output_size = MaybeUninit::uninit(); + let ret = unsafe { ext_ffi::get_balance(purse_ptr, purse_size, output_size.as_mut_ptr()) }; + match api_error::result_from(ret) { + Ok(_) => unsafe { output_size.assume_init() }, + Err(ApiError::InvalidPurse) => return None, + Err(error) => runtime::revert(error), + } + }; + let value_bytes = runtime::read_host_buffer(value_size).unwrap_or_revert(); + let value: U512 = bytesrepr::deserialize(value_bytes).unwrap_or_revert(); + Some(value) +} + +/// Transfers `amount` of motes from the default purse of the account to `target` +/// account. If `target` does not exist it will be created. +pub fn transfer_to_account(target: AccountHash, amount: U512) -> TransferResult { + let (target_ptr, target_size, _bytes1) = contract_api::to_ptr(target); + let (amount_ptr, amount_size, _bytes2) = contract_api::to_ptr(amount); + let return_code = + unsafe { ext_ffi::transfer_to_account(target_ptr, target_size, amount_ptr, amount_size) }; + TransferredTo::result_from(return_code) +} + +/// Transfers `amount` of motes from `source` purse to `target` account. If `target` does not exist +/// it will be created. +pub fn transfer_from_purse_to_account( + source: URef, + target: AccountHash, + amount: U512, +) -> TransferResult { + let (source_ptr, source_size, _bytes1) = contract_api::to_ptr(source); + let (target_ptr, target_size, _bytes2) = contract_api::to_ptr(target); + let (amount_ptr, amount_size, _bytes3) = contract_api::to_ptr(amount); + let return_code = unsafe { + ext_ffi::transfer_from_purse_to_account( + source_ptr, + source_size, + target_ptr, + target_size, + amount_ptr, + amount_size, + ) + }; + TransferredTo::result_from(return_code) +} + +/// Transfers `amount` of motes from `source` purse to `target` purse. If `target` does not exist +/// the transfer fails. +pub fn transfer_from_purse_to_purse( + source: URef, + target: URef, + amount: U512, +) -> Result<(), ApiError> { + let (source_ptr, source_size, _bytes1) = contract_api::to_ptr(source); + let (target_ptr, target_size, _bytes2) = contract_api::to_ptr(target); + let (amount_ptr, amount_size, _bytes3) = contract_api::to_ptr(amount); + let result = unsafe { + ext_ffi::transfer_from_purse_to_purse( + source_ptr, + source_size, + target_ptr, + target_size, + amount_ptr, + amount_size, + ) + }; + if result == 0 { + Ok(()) + } else { + Err(ApiError::Transfer) + } +} diff --git a/smart-contracts/contract/src/ext_ffi.rs b/smart-contracts/contract/src/ext_ffi.rs new file mode 100644 index 0000000000..6fa2299032 --- /dev/null +++ b/smart-contracts/contract/src/ext_ffi.rs @@ -0,0 +1,638 @@ +//! Contains low-level bindings for host-side ("external") functions. +//! +//! Generally should not be used directly. See the [`contract_api`](crate::contract_api) for +//! high-level bindings suitable for writing smart contracts. +extern "C" { + /// The bytes in the span of wasm memory from `key_ptr` to `key_ptr + key_size` must correspond + /// to a valid global state key, otherwise the function will fail. If the key is de-serialized + /// successfully, then the result of the read is serialized and buffered in the runtime. This + /// result can be obtained via the [`read_host_buffer`] function. Returns standard error code. + /// + /// # Arguments + /// + /// * `key_ptr` - pointer (offset in wasm linear memory) to serialized form of the key to read + /// * `key_size` - size of the serialized key (in bytes) + /// * `output_size` - pointer to a value where host will write size of bytes read from given key + pub fn read_value(key_ptr: *const u8, key_size: usize, output_size: *mut usize) -> i32; + /// The bytes in wasm memory from offset `key_ptr` to `key_ptr + key_size` + /// will be used together with the current context’s seed to form a local key. + /// The value at that local key is read from the global state, serialized and + /// buffered in the runtime. This result can be obtained via the [`read_host_buffer`] + /// function. + /// + /// # Arguments + /// + /// * `key_ptr` - pointer to bytes representing the user-defined key + /// * `key_size` - size of the key (in bytes) + /// * `output_size` - pointer to a value where host will write size of bytes read from given key + pub fn read_value_local(key_ptr: *const u8, key_size: usize, output_size: *mut usize) -> i32; + /// This function writes the provided value (read via de-serializing the bytes + /// in wasm memory from offset `value_ptr` to `value_ptr + value_size`) under + /// the provided key (read via de-serializing the bytes in wasm memory from + /// offset `key_ptr` to `key_ptr + key_size`) in the global state. This + /// function will cause a `Trap` if the key or value fail to de-serialize or + /// if writing to that key is not permitted. + /// + /// # Arguments + /// + /// * `key_ptr` - pointer to bytes representing the key to write to + /// * `key_size` - size of the key (in bytes) + /// * `value_ptr` - pointer to bytes representing the value to write at the key + /// * `value_size` - size of the value (in bytes) + pub fn write(key_ptr: *const u8, key_size: usize, value_ptr: *const u8, value_size: usize); + + /// The bytes in wasm memory from offset `key_ptr` to `key_ptr + key_size` + /// will be used together with the current context’s seed to form a local key. + /// This function writes the provided value (read via de-serializing the bytes + /// in wasm memory from offset `value_ptr` to `value_ptr + value_size`) under + /// that local key in the global state. This function will cause a `Trap` if + /// the value fails to de-serialize. + /// + /// # Arguments + /// + /// * `key_ptr` - pointer to bytes representing the user-defined key to write to + /// * `key_size` - size of the key (in bytes) + /// * `value_ptr` - pointer to bytes representing the value to write at the key + /// * `value_size` - size of the value (in bytes) + pub fn write_local( + key_ptr: *const u8, + key_size: usize, + value_ptr: *const u8, + value_size: usize, + ); + /// This function adds the provided value (read via de-serializing the bytes + /// in wasm memory from offset `value_ptr` to `value_ptr + value_size`) to the + /// current value under the provided key (read via de-serializing the bytes in + /// wasm memory from offset `key_ptr` to `key_ptr + key_size`) in the global + /// state. This function will cause a `Trap` if the key or value fail to + /// de-serialize or if adding to that key is not permitted, or no value + /// presently exists at that key. + /// + /// # Arguments + /// + /// * `key_ptr` - pointer to bytes representing the key to write to + /// * `key_size` - size of the key (in bytes) + /// * `value_ptr` - pointer to bytes representing the value to write at the key + /// * `value_size` - size of the value (in bytes) + pub fn add(key_ptr: *const u8, key_size: usize, value_ptr: *const u8, value_size: usize); + /// + pub fn add_local(key_ptr: *const u8, key_size: usize, value_ptr: *const u8, value_size: usize); + /// This function causes the runtime to generate a new [`casperlabs_types::uref::URef`], with + /// the provided value stored under it in the global state. The new + /// [`casperlabs_types::uref::URef`] is written (in serialized form) to the wasm linear + /// memory starting from the `key_ptr` offset. Note that data corruption is possible if not + /// enough memory is allocated for the [`casperlabs_types::uref::URef`] at `key_ptr`. This + /// function will cause a `Trap` if the bytes in wasm memory from offset `value_ptr` to + /// `value_ptr + value_size` cannot be de-serialized into a `Value`. + /// + /// # Arguments + /// + /// * `key_ptr` - pointer to the offset in wasm memory where the new + /// [`casperlabs_types::uref::URef`] will be written + /// * `value_ptr` - pointer to bytes representing the value to write under the new + /// [`casperlabs_types::uref::URef`] + /// * `value_size` - size of the value (in bytes) + pub fn new_uref(uref_ptr: *mut u8, value_ptr: *const u8, value_size: usize); + /// + pub fn load_named_keys(total_keys: *mut usize, result_size: *mut usize) -> i32; + /// This function causes a `Trap`, terminating the currently running module, + /// but first copies the bytes from `value_ptr` to `value_ptr + value_size` to + /// a buffer which is returned to the calling module (if this module was + /// invoked by [`call_contract`] or [`call_versioned_contract`]). Additionally, the known + /// [`casperlabs_types::uref::URef`]s of the calling context are augmented with the + /// [`casperlabs_types::uref::URef`]s de-serialized from wasm memory offset + /// `extra_urefs_ptr` to `extra_urefs_ptr + extra_urefs_size`. This function will cause a + /// `Trap` if the bytes at `extra_urefs_ptr` cannot be de-serialized as type `Vec`, or + /// if any of the extra [`casperlabs_types::uref::URef`]s are invalid in the current + /// context. + /// + /// # Arguments + /// + /// * `value_ptr`: pointer to bytes representing the value to return to the caller + /// * `value_size`: size of the value (in bytes) + pub fn ret(value_ptr: *const u8, value_size: usize) -> !; + /// + pub fn get_key( + name_ptr: *const u8, + name_size: usize, + output_ptr: *mut u8, + output_size: usize, + bytes_written_ptr: *mut usize, + ) -> i32; + /// + pub fn has_key(name_ptr: *const u8, name_size: usize) -> i32; + /// + pub fn put_key(name_ptr: *const u8, name_size: usize, key_ptr: *const u8, key_size: usize); + /// + pub fn remove_key(name_ptr: *const u8, name_size: usize); + /// This function causes a `Trap` which terminates the currently running + /// module. Additionally, it signals that the current entire phase of + /// execution of the deploy should be terminated as well, and that the effects + /// of the execution up to this point should be reverted. The error code + /// provided to this function will be included in the error message of the + /// deploy in the block in which it is included. + /// + /// # Arguments + /// + /// * `status` - error code of the revert + pub fn revert(status: u32) -> !; + /// This function checks if all the keys contained in the given `Value` are + /// valid in the current context (i.e. the `Value` does not contain any forged + /// [`casperlabs_types::uref::URef`]s). This function causes a `Trap` if the bytes in wasm + /// memory from offset `value_ptr` to `value_ptr + value_size` cannot be de-serialized as + /// type `Value`. + pub fn is_valid_uref(uref_ptr: *const u8, uref_size: usize) -> i32; + /// This function attempts to add the given public key as an associated key to + /// the current account. Presently only 32-byte keys are supported; it is up + /// to the caller to ensure that the 32-bytes starting from offset + /// `public_key` represent the key they wish to add. Weights are internally + /// represented by a `u8`, this function will cause a `Trap` if the weight is + /// not between 0 and 255 inclusively. The result returned is a status code + /// for adding the key where 0 represents success, 1 means no more keys can be + /// added to this account (only 10 keys can be added), 2 means the key is + /// already associated (if you wish to change the weight of an associated key + /// then used [`update_associated_key`]), and 3 means permission denied (this + /// could be because the function was called outside of session code or + /// because the key management threshold was not met by the keys authorizing + /// the deploy). + /// + /// Returns status code for adding the key, where 0 represents success and non-zero represents + /// failure. + /// + /// # Arguments + /// + /// * `public_key` - pointer to the bytes in wasm memory representing the public key to add, + /// presently only 32-byte public keys are supported. + /// * `weight` - the weight to assign to this public key + pub fn add_associated_key( + account_hash_ptr: *const u8, + account_hash_size: usize, + weight: i32, + ) -> i32; + /// This function attempts to remove the given public key from the associated + /// keys of the current account. Presently only 32-byte keys are supported; it + /// is up to the caller to ensure that the 32-bytes starting from offset + /// `public_key` represent the key they wish to remove. The result returned is + /// a status code for adding the key where 0 represents success, 1 means the + /// key was not associated to begin with, 2 means means permission denied + /// (this could be because the function was called outside of session code or + /// because the key management threshold was not met by the keys authorizing + /// the deploy), and 3 means this key cannot be removed because otherwise it + /// would be impossible to meet either the deploy or key management + /// thresholds. + /// + /// Returns status code for adding the key, where 0 represents success and non-zero represents + /// failure. + /// + /// # Arguments + /// + /// * `public_key` - pointer to the bytes in wasm memory representing the public key to update, + /// presently only 32-byte public keys are supported. + /// * `weight` - the weight to assign to this public key + pub fn remove_associated_key(account_hash_ptr: *const u8, account_hash_size: usize) -> i32; + /// This function attempts to update the given public key as an associated key + /// to the current account. Presently only 32-byte keys are supported; it is + /// up to the caller to ensure that the 32-bytes starting from offset + /// `public_key` represent the key they wish to add. Weights are internally + /// represented by a `u8`, this function will cause a `Trap` if the weight is + /// not between 0 and 255 inclusively. The result returned is a status code + /// for adding the key where 0 represents success, 1 means the key was not + /// associated to the account (to add a new key use `add_associated_key`), 2 + /// means means permission denied (this could be because the function was + /// called outside of session code or because the key management threshold was + /// not met by the keys authorizing the deploy), and 3 means this key cannot + /// be changed to the specified weight because then it would be impossible to + /// meet either the deploy or key management thresholds (you may wish to try + /// again with a higher weight or after lowering the action thresholds). + /// + /// # Arguments + /// + /// * `public_key` - pointer to the bytes in wasm memory representing the + /// public key to update, presently only 32-byte public keys are supported + /// * `weight` - the weight to assign to this public key + pub fn update_associated_key( + account_hash_ptr: *const u8, + account_hash_size: usize, + weight: i32, + ) -> i32; + /// This function changes the threshold to perform the specified action. The + /// action index is interpreted as follows: 0 means deployment and 1 means key + /// management. Thresholds are represented internally as a `u8`, this function + /// will cause a `Trap` if the new threshold is not between 0 and 255 + /// inclusively. The return value is a status code where 0 means success, 1 + /// means the key management threshold cannot be set lower than the deploy + /// threshold, 2 means the deployment threshold cannot be set higher than the + /// key management threshold, 3 means permission denied (this could be because + /// the function was called outside of session code or because the key + /// management threshold was not met by the keys authorizing the deploy), and + /// 4 means the threshold would be set higher than the total weight of + /// associated keys (and therefore would be impossible to meet). + /// + /// # Arguments + /// + /// * `action` - index representing the action threshold to set + /// * `threshold` - new value of the threshold for performing this action + pub fn set_action_threshold(permission_level: u32, threshold: u32) -> i32; + /// This function returns the public key of the account for this deploy. The + /// result is always 36-bytes in length (4 bytes prefix on a 32-byte public + /// key); it is up to the caller to ensure the right amount of memory is + /// allocated at `dest_ptr`, data corruption in the wasm memory could occur + /// otherwise. + /// + /// # Arguments + /// + /// * `dest_ptr` - pointer to position in wasm memory where to write the result + pub fn get_caller(output_size: *mut usize) -> i32; + /// This function gets the timestamp which will be in the block this deploy is + /// included in. The return value is always a 64-bit unsigned integer, + /// representing the number of milliseconds since the Unix epoch. It is up to + /// the caller to ensure there are 8 bytes allocated at `dest_ptr`, otherwise + /// data corruption in the wasm memory may occur. + /// + /// # Arguments + /// + /// * `dest_ptr` - pointer in wasm memory where to write the result + pub fn get_blocktime(dest_ptr: *const u8); + /// This function uses the mint contract to create a new, empty purse. If the + /// call is successful then the [`casperlabs_types::uref::URef`] (in serialized form) is written + /// to the indicated place in wasm memory. It is up to the caller to ensure at + /// least `purse_size` bytes are allocated at `purse_ptr`, otherwise + /// data corruption may occur. This function causes a `Trap` if + /// `purse_size` is not equal to 38. + /// + /// # Arguments + /// + /// * `purse_ptr` - pointer to position in wasm memory where to write the created + /// [`casperlabs_types::uref::URef`] + /// * `purse_size` - allocated size for the [`casperlabs_types::uref::URef`] + pub fn create_purse(purse_ptr: *const u8, purse_size: usize) -> i32; + /// This function uses the mint contract’s transfer function to transfer + /// tokens from the current account’s main purse to the main purse of the + /// target account. If the target account does not exist then it is + /// automatically created, and the tokens are transferred to the main purse of + /// the new account. The target is a serialized `PublicKey` (i.e. 36 bytes + /// where the first 4 bytes are the number `32` in little endian encoding, and + /// the remaining 32-bytes are the public key). The amount must be a + /// serialized 512-bit unsigned integer. This function causes a `Trap` if the + /// target cannot be de-serialized as a `PublicKey` or the amount cannot be + /// de-serialized into a `U512`. The return value indicated what occurred, + /// where 0 means a successful transfer to an existing account, 1 means a + /// successful transfer to a new account, and 2 means the transfer failed + /// (this could be because the current account’s main purse had insufficient + /// tokens or because the function was called outside of session code and so + /// does not have access to the account’s main purse). + /// + /// # Arguments + /// + /// * `target_ptr` - pointer in wasm memory to bytes representing the target account to transfer + /// to + /// * `target_size` - size of the target (in bytes) + /// * `amount_ptr` - pointer in wasm memory to bytes representing the amount to transfer to the + /// target account + /// * `amount_size` - size of the amount (in bytes) + pub fn transfer_to_account( + target_ptr: *const u8, + target_size: usize, + amount_ptr: *const u8, + amount_size: usize, + ) -> i32; + /// This function uses the mint contract’s transfer function to transfer + /// tokens from the specified purse to the main purse of the target account. + /// If the target account does not exist then it is automatically created, and + /// the tokens are transferred to the main purse of the new account. The + /// source is a serialized [`casperlabs_types::uref::URef`]. + /// The target is a serialized `PublicKey` (i.e. 36 bytes where the + /// first 4 bytes are the number `32` in little endian encoding, and the + /// remaining 32-bytes are the public key). The amount must be a serialized + /// 512-bit unsigned integer. This function causes a `Trap` if the source + /// cannot be de-serialized as a [`casperlabs_types::uref::URef`], or the target cannot be + /// de-serialized as a `PublicKey` or the amount cannot be de-serialized into + /// a `U512`. The return value indicated what occurred, where 0 means a + /// successful transfer to an existing account, 1 means a successful transfer + /// to a new account, and 2 means the transfer failed (this could be because + /// the source purse had insufficient tokens or because there was not valid + /// access to the source purse). + /// + /// # Arguments + /// + /// * `source_ptr` - pointer in wasm memory to bytes representing the source + /// [`casperlabs_types::uref::URef`] to transfer from + /// * `source_size` - size of the source [`casperlabs_types::uref::URef`] (in bytes) + /// * `target_ptr` - pointer in wasm memory to bytes representing the target account to transfer + /// to + /// * `target_size` - size of the target (in bytes) + /// * `amount_ptr` - pointer in wasm memory to bytes representing the amount to transfer to the + /// target account + /// * `amount_size` - size of the amount (in bytes) + pub fn transfer_from_purse_to_account( + source_ptr: *const u8, + source_size: usize, + target_ptr: *const u8, + target_size: usize, + amount_ptr: *const u8, + amount_size: usize, + ) -> i32; + /// This function uses the mint contract’s transfer function to transfer + /// tokens from the specified source purse to the specified target purse. If + /// the target account does not exist then it is automatically created, and + /// the tokens are transferred to the main purse of the new account. The + /// source is a serialized [`casperlabs_types::uref::URef`]. + /// The target is also a serialized [`casperlabs_types::uref::URef`]. The amount must be a + /// serialized 512-bit unsigned integer. This function causes a `Trap` if the + /// source or target cannot be de-serialized as a [`casperlabs_types::uref::URef`] or the amount + /// cannot be de-serialized into a `U512`. The return value indicated what + /// occurred, where 0 means a successful transfer, 1 means the transfer + /// failed (this could be because the source purse had insufficient tokens or + /// because there was not valid access to the source purse or target purse). + /// + /// # Arguments + /// + /// * `source_ptr` - pointer in wasm memory to bytes representing the source + /// [`casperlabs_types::uref::URef`] to transfer from + /// * `source_size` - size of the source [`casperlabs_types::uref::URef`] (in bytes) + /// * `target_ptr` - pointer in wasm memory to bytes representing the target + /// [`casperlabs_types::uref::URef`] to transfer to + /// * `target_size` - size of the target (in bytes) + /// * `amount_ptr` - pointer in wasm memory to bytes representing the amount to transfer to the + /// target account + /// * `amount_size` - size of the amount (in bytes) + pub fn transfer_from_purse_to_purse( + source_ptr: *const u8, + source_size: usize, + target_ptr: *const u8, + target_size: usize, + amount_ptr: *const u8, + amount_size: usize, + ) -> i32; + /// This function uses the mint contract's balance function to get the balance + /// of the specified purse. It causes a `Trap` if the bytes in wasm memory + /// from `purse_ptr` to `purse_ptr + purse_size` cannot be + /// de-serialized as a [`casperlabs_types::uref::URef`]. The return value is the size of the + /// result in bytes. The result is copied to the host buffer and thus can be obtained + /// by any function which copies the buffer into wasm memory (e.g. + /// `get_read`). The result bytes are serialized from type `Option` and + /// should be interpreted as such. + /// + /// # Arguments + /// + /// * `purse_ptr` - pointer in wasm memory to the bytes representing the + /// [`casperlabs_types::uref::URef`] of the purse to get the balance of + /// * `purse_size` - size of the [`casperlabs_types::uref::URef`] (in bytes) + pub fn get_balance(purse_ptr: *const u8, purse_size: usize, result_size: *mut usize) -> i32; + /// This function writes bytes representing the current phase of the deploy + /// execution to the specified pointer. The size of the result is always one + /// byte, it is up to the caller to ensure one byte of memory is allocated at + /// `dest_ptr`, otherwise data corruption in the wasm memory could occur. The + /// one byte is interpreted as follows: 0 means a system phase (should never + /// be encountered by user deploys), 1 means the payment phase, 2 means the + /// session phase and 3 means the finalization phase (should never be + /// encountered by user code). + /// + /// # Arguments + /// + /// * `dest_ptr` - pointer to position in wasm memory to write the result + pub fn get_phase(dest_ptr: *mut u8); + /// + pub fn get_system_contract( + system_contract_index: u32, + dest_ptr: *mut u8, + dest_size: usize, + ) -> i32; + /// + pub fn get_main_purse(dest_ptr: *mut u8); + /// This function copies the contents of the current runtime buffer into the + /// wasm memory, beginning at the provided offset. It is intended that this + /// function be called after a call to a function that uses host buffer. It is up to the caller + /// to ensure that the proper amount of memory is allocated for this write, + /// otherwise data corruption in the wasm memory may occur due to this call + /// overwriting some bytes unintentionally. The size of the data which will be + /// written is stored on the host. The bytes which are written are those corresponding to the + /// value returned by the called contract; it is up to the developer to know how to attempt + /// to interpret those bytes. + /// + /// # Arguments + /// + /// * `dest_ptr` - pointer (offset in wasm memory) to the location where the host buffer should + /// be written + /// * `dest_size` - size of output buffer + /// * `bytes_written` - a pointer to a value where amount of bytes written will be set + pub fn read_host_buffer(dest_ptr: *mut u8, dest_size: usize, bytes_written: *mut usize) -> i32; + /// Creates new contract package at hash. Returns both newly generated + /// [`casperlabs_types::ContractPackageHash`] and a [`casperlabs_types::URef`] for further + /// modifying access. + pub fn create_contract_package_at_hash(hash_addr_ptr: *mut u8, access_addr_ptr: *mut u8); + /// Creates new named contract user group under a contract package. + /// + /// # Arguments + /// + /// * `contract_package_hash_ptr` - pointer to serialized contract package hash. + /// * `contract_package_hash_size` - size of contract package hash in serialized form. + /// * `label_ptr` - serialized group label + /// * `label_size` - size of serialized group label + /// * `num_new_urefs` - amount of new urefs to be provisioned by host + /// * `existing_urefs_ptr` - serialized list of existing [`casperlabs_types::URef`]s + /// * `existing_urefs_size` - size of serialized list of [`casperlabs_types::URef`]s + /// * `output_size_ptr` - pointer to a value where a size of list of [`casperlabs_types::URef`]s + /// written to host buffer will be set. + pub fn create_contract_user_group( + contract_package_hash_ptr: *const u8, + contract_package_hash_size: usize, + label_ptr: *const u8, + label_size: usize, + num_new_urefs: u8, + existing_urefs_ptr: *const u8, + existing_urefs_size: usize, + output_size_ptr: *mut usize, + ) -> i32; + /// Adds new contract version to a contract package. + /// + /// # Arguments + /// + /// * `contract_package_hash_ptr` - pointer to serialized contract package hash. + /// * `contract_package_hash_size` - size of contract package hash in serialized form. + /// * `version_ptr` - output parameter where new version assigned by host is set + /// * `entry_points_ptr` - pointer to serialized [`casperlabs_types::EntryPoints`] + /// * `entry_points_size` - size of serialized [`casperlabs_types::EntryPoints`] + /// * `named_keys_ptr` - pointer to serialized [`casperlabs_types::contracts::NamedKeys`] + /// * `named_keys_size` - size of serialized [`casperlabs_types::contracts::NamedKeys`] + /// * `output_ptr` - pointer to a memory where host assigned contract hash is set to + /// * `output_size` - size of memory area that host can write to + /// * `bytes_written_ptr` - pointer to a value where host will set a number of bytes written to + /// the `output_size` pointer + pub fn add_contract_version( + contract_package_hash_ptr: *const u8, + contract_package_hash_size: usize, + version_ptr: *const u32, + entry_points_ptr: *const u8, + entry_points_size: usize, + named_keys_ptr: *const u8, + named_keys_size: usize, + output_ptr: *mut u8, + output_size: usize, + bytes_written_ptr: *mut usize, + ) -> i32; + /// Disables contract in a contract package. Returns non-zero standard error for a failure, + /// otherwise a zero indicates success. + /// + /// # Arguments + /// + /// * `contract_package_hash_ptr` - pointer to serialized contract package hash. + /// * `contract_package_hash_size` - size of contract package hash in serialized form. + /// * `contract_hash_ptr` - pointer to serialized contract hash. + /// * `contract_hash_size` - size of contract hash in serialized form. + pub fn disable_contract_version( + contract_package_hash_ptr: *const u8, + contract_package_hash_size: usize, + contract_hash_ptr: *const u8, + contract_hash_size: usize, + ) -> i32; + /// Calls a contract by its hash. Requires entry point name that has to be present on a + /// specified contract, and serialized named arguments. Returns a standard error code in + /// case of failure, otherwise a successful execution returns zero. Bytes returned from contract + /// execution are set to `result_size` pointer. + /// + /// # Arguments + /// * `contract_package_hash_ptr` - pointer to serialized contract package hash. + /// * `contract_package_hash_size` - size of contract package hash in serialized form. + /// * `entry_point_name_ptr` - pointer to serialized contract entry point name + /// * `entry_point_name_size` - size of serialized contract entry point name + /// * `runtime_args_ptr` - pointer to serialized runtime arguments + /// * `runtime_args_size` - size of serialized runtime arguments + /// * `result_size` - a pointer to a value which will be set to a size of bytes of called + /// contract return value + pub fn call_contract( + contract_hash_ptr: *const u8, + contract_hash_size: usize, + entry_point_name_ptr: *const u8, + entry_point_name_size: usize, + runtime_args_ptr: *const u8, + runtime_args_size: usize, + result_size: *mut usize, + ) -> i32; + /// Calls a contract by its package hash. Optionally accepts a serialized `Option` as a + /// version that for `None` case would call most recent version for given protocol version, + /// otherwise it selects a specific contract version. Requires an entry point name + /// registered in a given version of contract. Returns a standard error code in case of + /// failure, otherwise a successful execution returns zero. Bytes returned from contract + /// execution are set to `result_size` pointer + /// + /// # Arguments + /// + /// * `contract_package_hash_ptr` - pointer to serialized contract package hash. + /// * `contract_package_hash_size` - size of contract package hash in serialized form. + /// * `contract_version_ptr` - Contract package hash in a serialized form + /// * `contract_version_size` - + /// * `entry_point_name_ptr` - + /// * `entry_point_name_size` - + /// * `runtime_args_ptr` - + /// * `runtime_args_size` - + /// * `result_size` - + pub fn call_versioned_contract( + contract_package_hash_ptr: *const u8, + contract_package_hash_size: usize, + contract_version_ptr: *const u8, + contract_version_size: usize, + entry_point_name_ptr: *const u8, + entry_point_name_size: usize, + runtime_args_ptr: *const u8, + runtime_args_size: usize, + result_size: *mut usize, + ) -> i32; + /// This function queries the host side to check for given named argument existence and returns + /// a size in bytes of given argument. Returns zero for success or non-zero value for + /// failure as described in standard error codes. + /// + /// # Arguments + /// + /// * `name_ptr` - pointer (offset in wasm memory) to the location where serialized argument + /// name is present + /// * `name_size` - size of serialized bytes of argument name + /// * `dest_ptr` - pointer to the location where argument bytes will be copied from the host + /// side + /// * `dest_size` - size of destination pointer + pub fn get_named_arg_size(name_ptr: *const u8, name_size: usize, dest_size: *mut usize) -> i32; + /// This function copies the contents of the current runtime buffer into the + /// wasm memory, beginning at the provided offset. It is intended that this + /// function be called after a call to `load_arg`. It is up to the caller to + /// ensure that the proper amount of memory is allocated for this write, + /// otherwise data corruption in the wasm memory may occur due to this call + /// overwriting some bytes unintentionally. The size of the data which will be + /// written is returned from the `load_arg` call. The bytes which are written + /// are the those corresponding to the provided argument; it is up to the + /// developer to know how to attempt to interpret those bytes. + /// + /// # Arguments + /// + /// * `name_ptr` - pointer (offset in wasm memory) to the location where serialized argument + /// name is present + /// * `name_size` - size of serialized bytes of argument name + /// * `dest_ptr` - pointer to the location where argument bytes will be copied from the host + /// side + /// * `dest_size` - size of destination pointer + pub fn get_named_arg( + name_ptr: *const u8, + name_size: usize, + dest_ptr: *mut u8, + dest_size: usize, + ) -> i32; + /// Removes group from given contract package. + /// + /// # Arguments + /// + /// * `contract_package_hash_ptr` - pointer to serialized contract package hash. + /// * `contract_package_hash_size` - size of contract package hash in serialized form. + /// * `label_ptr` - serialized group label + /// * `label_size` - size of serialized group label + pub fn remove_contract_user_group( + contract_package_hash_ptr: *const u8, + contract_package_hash_size: usize, + label_ptr: *const u8, + label_size: usize, + ) -> i32; + /// Requests host to provision additional [`casperlabs_types::URef`] to a specified group + /// identified by its label. Returns standard error code for non-zero value, otherwise zero + /// indicated success. + /// + /// # Arguments + /// + /// * `contract_package_hash_ptr` - pointer to serialized contract package hash. + /// * `contract_package_hash_size` - size of contract package hash in serialized form. + /// * `label_ptr` - serialized group label + /// * `label_size` - size of serialized group label + /// * `value_size_ptr` - size of data written to a host buffer will be saved here + pub fn provision_contract_user_group_uref( + contract_package_hash_ptr: *const u8, + contract_package_hash_size: usize, + label_ptr: *const u8, + label_size: usize, + value_size_ptr: *const usize, + ) -> i32; + /// Removes user group urefs. Accepts a contract package hash, label name of a group, and a list + /// of urefs that will be removed from the group. + /// + /// # Arguments + /// + /// * `contract_package_hash_ptr` - pointer to serialized contract package hash. + /// * `contract_package_hash_size` - size of contract package hash in serialized form. + /// * `label_ptr` - serialized group label + /// * `label_size` - size of serialized group label + /// * `urefs_ptr` - pointer to serialized list of urefs + /// * `urefs_size` - size of serialized list of urefs + pub fn remove_contract_user_group_urefs( + contract_package_hash_ptr: *const u8, + contract_package_hash_size: usize, + label_ptr: *const u8, + label_size: usize, + urefs_ptr: *const u8, + urefs_size: usize, + ) -> i32; + + /// Prints data directly to stanadard output on the host. + /// + /// # Arguments + /// + /// * `text_ptr` - pointer to serialized text to print + /// * `text_size` - size of serialized text to print + #[cfg(feature = "test-support")] + pub fn print(text_ptr: *const u8, text_size: usize); +} diff --git a/smart-contracts/contract/src/handlers.rs b/smart-contracts/contract/src/handlers.rs new file mode 100644 index 0000000000..1b09b7b0cb --- /dev/null +++ b/smart-contracts/contract/src/handlers.rs @@ -0,0 +1,26 @@ +//! Contains definitions for panic and allocation error handlers, along with other `no_std` support +//! code. +#[cfg(feature = "test-support")] +use crate::contract_api::runtime; +#[cfg(feature = "test-support")] +use alloc::format; + +/// A panic handler for use in a `no_std` environment which simply aborts the process. +#[panic_handler] +#[no_mangle] +pub fn panic(_info: &::core::panic::PanicInfo) -> ! { + #[cfg(feature = "test-support")] + runtime::print(&format!("Panic: {}", _info)); + ::core::intrinsics::abort(); +} + +/// An out-of-memory allocation error handler for use in a `no_std` environment which simply aborts +/// the process. +#[alloc_error_handler] +#[no_mangle] +pub fn oom(_: ::core::alloc::Layout) -> ! { + ::core::intrinsics::abort(); +} + +#[lang = "eh_personality"] +extern "C" fn eh_personality() {} diff --git a/smart-contracts/contract/src/lib.rs b/smart-contracts/contract/src/lib.rs new file mode 100644 index 0000000000..388a9dbc16 --- /dev/null +++ b/smart-contracts/contract/src/lib.rs @@ -0,0 +1,78 @@ +//! A Rust library for writing smart contracts on the +//! [CasperLabs Platform](https://techspec.casperlabs.io). +//! +//! # `no_std` +//! +//! By default, the library is `no_std`, however you can enable full `std` functionality by enabling +//! the crate's `std` feature. +//! +//! # Example +//! +//! The following example contains session code which persists an integer value under an unforgeable +//! reference. It then stores the unforgeable reference under a name in context-local storage. +//! +//! ```rust,no_run +//! #![no_std] +//! +//! use casperlabs_contract::{ +//! contract_api::{runtime, storage}, +//! }; +//! use casperlabs_types::{Key, URef}; +//! +//! const KEY: &str = "special_value"; +//! const ARG_VALUE: &str = "value"; +//! +//! fn store(value: i32) { +//! // Store `value` under a new unforgeable reference. +//! let value_ref: URef = storage::new_uref(value); +//! +//! // Wrap the unforgeable reference in a value of type `Key`. +//! let value_key: Key = value_ref.into(); +//! +//! // Store this key under the name "special_value" in context-local storage. +//! runtime::put_key(KEY, value_key); +//! } +//! +//! // All session code must have a `call` entrypoint. +//! #[no_mangle] +//! pub extern "C" fn call() { +//! // Get the optional first argument supplied to the argument. +//! let value: i32 = runtime::get_named_arg(ARG_VALUE); +//! store(value); +//! } +//! # fn main() {} +//! ``` +//! +//! # Writing Smart Contracts +//! +//! Support for writing smart contracts are contained in the [`contract_api`] module and its +//! submodules. + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr( + not(feature = "std"), + feature(alloc_error_handler, core_intrinsics, lang_items) +)] +#![doc(html_root_url = "https://docs.rs/casperlabs-contract/0.6.0")] +#![doc( + html_favicon_url = "https://raw.githubusercontent.com/CasperLabs/CasperLabs/dev/images/CasperLabs_Logo_Favicon_RGB_50px.png", + html_logo_url = "https://raw.githubusercontent.com/CasperLabs/CasperLabs/dev/images/CasperLabs_Logo_Symbol_RGB.png", + test(attr(forbid(warnings))) +)] +#![warn(missing_docs)] + +extern crate alloc; +#[cfg(any(feature = "std", test))] +extern crate std; + +/// An instance of [`WeeAlloc`](https://docs.rs/wee_alloc) which allows contracts built as `no_std` +/// to avoid having to provide a global allocator themselves. +#[cfg(not(any(feature = "std", test)))] +#[global_allocator] +pub static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +pub mod contract_api; +pub mod ext_ffi; +#[cfg(not(any(feature = "std", test)))] +pub mod handlers; +pub mod unwrap_or_revert; diff --git a/smart-contracts/contract/src/unwrap_or_revert.rs b/smart-contracts/contract/src/unwrap_or_revert.rs new file mode 100644 index 0000000000..b1a92090fe --- /dev/null +++ b/smart-contracts/contract/src/unwrap_or_revert.rs @@ -0,0 +1,37 @@ +//! Home of [`UnwrapOrRevert`], a convenience trait for unwrapping values. + +use casperlabs_types::ApiError; + +use crate::contract_api::runtime; + +/// A trait which provides syntactic sugar for unwrapping a type or calling +/// [`runtime::revert`] if this fails. It is implemented for `Result` and `Option`. +pub trait UnwrapOrRevert { + /// Unwraps the value into its inner type or calls [`runtime::revert`] with a + /// predetermined error code on failure. + fn unwrap_or_revert(self) -> T; + + /// Unwraps the value into its inner type or calls [`runtime::revert`] with the + /// provided `error` on failure. + fn unwrap_or_revert_with>(self, error: E) -> T; +} + +impl> UnwrapOrRevert for Result { + fn unwrap_or_revert(self) -> T { + self.unwrap_or_else(|error| runtime::revert(error.into())) + } + + fn unwrap_or_revert_with>(self, error: F) -> T { + self.unwrap_or_else(|_| runtime::revert(error.into())) + } +} + +impl UnwrapOrRevert for Option { + fn unwrap_or_revert(self) -> T { + self.unwrap_or_else(|| runtime::revert(ApiError::None)) + } + + fn unwrap_or_revert_with>(self, error: E) -> T { + self.unwrap_or_else(|| runtime::revert(error.into())) + } +} diff --git a/smart-contracts/contract/tests/version_numbers.rs b/smart-contracts/contract/tests/version_numbers.rs new file mode 100644 index 0000000000..9f1d04aae3 --- /dev/null +++ b/smart-contracts/contract/tests/version_numbers.rs @@ -0,0 +1,4 @@ +#[test] +fn test_html_root_url() { + version_sync::assert_html_root_url_updated!("src/lib.rs"); +} diff --git a/smart-contracts/contracts-as/.gitignore b/smart-contracts/contracts-as/.gitignore new file mode 100644 index 0000000000..0a7ee2e21e --- /dev/null +++ b/smart-contracts/contracts-as/.gitignore @@ -0,0 +1,99 @@ +package-lock.json + +# Created by https://www.gitignore.io/api/node +# Edit at https://www.gitignore.io/?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# react / gatsby +public/ + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# End of https://www.gitignore.io/api/node diff --git a/smart-contracts/contracts-as/client/bonding/assembly/index.ts b/smart-contracts/contracts-as/client/bonding/assembly/index.ts new file mode 100644 index 0000000000..2fa3f01c8d --- /dev/null +++ b/smart-contracts/contracts-as/client/bonding/assembly/index.ts @@ -0,0 +1,49 @@ +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {U512} from "../../../../contract-as/assembly/bignum"; +import {CLValue} from "../../../../contract-as/assembly/clvalue"; +import {getMainPurse} from "../../../../contract-as/assembly/account"; +import {createPurse, transferFromPurseToPurse} from "../../../../contract-as/assembly/purse"; +import {RuntimeArgs} from "../../../../contract-as/assembly/runtime_args"; +import {Pair} from "../../../../contract-as/assembly/pair"; + +const POS_ACTION = "bond"; +const ARG_AMOUNT = "amount"; +const ARG_PURSE = "purse"; + +export function call(): void { + let proofOfStake = CL.getSystemContract(CL.SystemContract.ProofOfStake); + let mainPurse = getMainPurse(); + let bondingPurse = createPurse(); + + let amountBytes = CL.getNamedArg(ARG_AMOUNT); + if (amountBytes === null) { + Error.fromErrorCode(ErrorCode.MissingArgument).revert(); + return; + } + + let amountResult = U512.fromBytes(amountBytes); + if (amountResult.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + + let amount = amountResult.value; + + let ret = transferFromPurseToPurse( + mainPurse, + bondingPurse, + amount, + ); + if (ret > 0) { + Error.fromErrorCode(ErrorCode.Transfer).revert(); + return; + } + + let bondingPurseValue = CLValue.fromURef(bondingPurse); + let runtimeArgs = RuntimeArgs.fromArray([ + new Pair(ARG_AMOUNT, CLValue.fromU512(amount)), + new Pair(ARG_PURSE, bondingPurseValue), + ]); + CL.callContract(proofOfStake, POS_ACTION, runtimeArgs); +} diff --git a/smart-contracts/contracts-as/client/bonding/assembly/tsconfig.json b/smart-contracts/contracts-as/client/bonding/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/client/bonding/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/client/bonding/index.js b/smart-contracts/contracts-as/client/bonding/index.js new file mode 100644 index 0000000000..d93882dbe3 --- /dev/null +++ b/smart-contracts/contracts-as/client/bonding/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/bonding.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/client/bonding/package.json b/smart-contracts/contracts-as/client/bonding/package.json new file mode 100644 index 0000000000..9c4a6164eb --- /dev/null +++ b/smart-contracts/contracts-as/client/bonding/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/bonding.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/client/named-purse-payment/assembly/index.ts b/smart-contracts/contracts-as/client/named-purse-payment/assembly/index.ts new file mode 100644 index 0000000000..767a8f3788 --- /dev/null +++ b/smart-contracts/contracts-as/client/named-purse-payment/assembly/index.ts @@ -0,0 +1,88 @@ +import * as CL from "../../../../contract-as/assembly"; +import {getKey} from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {CLValue} from "../../../../contract-as/assembly/clvalue"; +import {U512} from "../../../../contract-as/assembly/bignum"; +import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; +import {URef} from "../../../../contract-as/assembly/uref"; +import {KeyVariant} from "../../../../contract-as/assembly/key"; +import {transferFromPurseToPurse} from "../../../../contract-as/assembly/purse"; +import {RuntimeArgs} from "../../../../contract-as/assembly/runtime_args"; +import {Pair} from "../../../../contract-as/assembly/pair"; + +const GET_PAYMENT_PURSE = "get_payment_purse"; +const SET_REFUND_PURSE= "set_refund_purse"; +const ARG_AMOUNT = "amount"; +const ARG_PURSE = "purse"; +const ARG_PURSE_NAME = "purse_name"; + +function getPurseURef(): URef | null{ + let purseNameBytes = CL.getNamedArg(ARG_PURSE_NAME); + + let purseName = fromBytesString(purseNameBytes); + if (purseName.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidArgument); + return null; + } + + let purseKey = getKey(purseName.value); + if (purseKey === null) { + Error.fromErrorCode(ErrorCode.InvalidArgument); + return null; + } + + if (purseKey.variant != KeyVariant.UREF_ID) { + Error.fromErrorCode(ErrorCode.UnexpectedKeyVariant); + return null; + } + + let purse = purseKey.uref; + + return purse; +} + +export function call(): void { + let maybePurseURef = getPurseURef(); + if (maybePurseURef === null) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + + let purseURef = maybePurseURef; + + let amountBytes = CL.getNamedArg(ARG_AMOUNT); + let amountResult = U512.fromBytes(amountBytes); + if (amountResult.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + let amount = amountResult.value; + + let proofOfStake = CL.getSystemContract(CL.SystemContract.ProofOfStake); + + // Get Payment Purse + let paymentPurseOutput = CL.callContract(proofOfStake, GET_PAYMENT_PURSE, new RuntimeArgs()); + + let paymentPurseResult = URef.fromBytes(paymentPurseOutput); + if (paymentPurseResult.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidPurse).revert(); + return; + } + let paymentPurse = paymentPurseResult.value; + + // Set Refund Purse + let runtimeArgs = RuntimeArgs.fromArray([ + new Pair(ARG_PURSE, CLValue.fromURef(purseURef)), + ]); + CL.callContract(proofOfStake, SET_REFUND_PURSE, runtimeArgs); + + let ret = transferFromPurseToPurse( + purseURef, + paymentPurse, + amount, + ); + if (ret > 0) { + Error.fromErrorCode(ErrorCode.Transfer).revert(); + return; + } +} diff --git a/smart-contracts/contracts-as/client/named-purse-payment/assembly/tsconfig.json b/smart-contracts/contracts-as/client/named-purse-payment/assembly/tsconfig.json new file mode 100644 index 0000000000..891cc238d0 --- /dev/null +++ b/smart-contracts/contracts-as/client/named-purse-payment/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/client/named-purse-payment/index.js b/smart-contracts/contracts-as/client/named-purse-payment/index.js new file mode 100644 index 0000000000..710b5d66b2 --- /dev/null +++ b/smart-contracts/contracts-as/client/named-purse-payment/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/named_purse_payment.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/client/named-purse-payment/package.json b/smart-contracts/contracts-as/client/named-purse-payment/package.json new file mode 100644 index 0000000000..86c75bd4b5 --- /dev/null +++ b/smart-contracts/contracts-as/client/named-purse-payment/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/named_purse_payment.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/client/revert/assembly/index.ts b/smart-contracts/contracts-as/client/revert/assembly/index.ts new file mode 100644 index 0000000000..9aa47988e2 --- /dev/null +++ b/smart-contracts/contracts-as/client/revert/assembly/index.ts @@ -0,0 +1,5 @@ +import {Error} from "../../../../contract-as/assembly/error"; + +export function call(): void { + Error.fromUserError(100).revert(); +} diff --git a/smart-contracts/contracts-as/client/revert/assembly/tsconfig.json b/smart-contracts/contracts-as/client/revert/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/client/revert/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/client/revert/index.js b/smart-contracts/contracts-as/client/revert/index.js new file mode 100644 index 0000000000..8a4fc0eef1 --- /dev/null +++ b/smart-contracts/contracts-as/client/revert/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/revert.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/client/revert/package.json b/smart-contracts/contracts-as/client/revert/package.json new file mode 100644 index 0000000000..06088a4c17 --- /dev/null +++ b/smart-contracts/contracts-as/client/revert/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/revert.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/index.ts b/smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/index.ts new file mode 100644 index 0000000000..fe1d05bd53 --- /dev/null +++ b/smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/index.ts @@ -0,0 +1,27 @@ +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {U512} from "../../../../contract-as/assembly/bignum"; +import {getMainPurse} from "../../../../contract-as/assembly/account"; +import {transferFromPurseToAccount, TransferredTo} from "../../../../contract-as/assembly/purse"; + +const ARG_TARGET = "target"; +const ARG_AMOUNT = "amount"; + + +export function call(): void { + const accountBytes = CL.getNamedArg(ARG_TARGET); + const amountBytes = CL.getNamedArg(ARG_AMOUNT); + const amountResult = U512.fromBytes(amountBytes); + if (amountResult.hasError()){ + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + let amount = amountResult.value; + const mainPurse = getMainPurse(); + + const result = transferFromPurseToAccount(mainPurse, accountBytes, amount); + if (result == TransferredTo.TransferError){ + Error.fromErrorCode(ErrorCode.Transfer).revert(); + return; + } +} diff --git a/smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/tsconfig.json b/smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/tsconfig.json new file mode 100644 index 0000000000..891cc238d0 --- /dev/null +++ b/smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/client/transfer-to-account-u512/index.js b/smart-contracts/contracts-as/client/transfer-to-account-u512/index.js new file mode 100644 index 0000000000..c282f33262 --- /dev/null +++ b/smart-contracts/contracts-as/client/transfer-to-account-u512/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/transfer_to_account_u512.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/client/transfer-to-account-u512/package.json b/smart-contracts/contracts-as/client/transfer-to-account-u512/package.json new file mode 100644 index 0000000000..04281b677b --- /dev/null +++ b/smart-contracts/contracts-as/client/transfer-to-account-u512/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/transfer_to_account_u512.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/client/unbonding/assembly/index.ts b/smart-contracts/contracts-as/client/unbonding/assembly/index.ts new file mode 100644 index 0000000000..bc173d8566 --- /dev/null +++ b/smart-contracts/contracts-as/client/unbonding/assembly/index.ts @@ -0,0 +1,30 @@ +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {U512} from "../../../../contract-as/assembly/bignum"; +import {CLValue} from "../../../../contract-as/assembly/clvalue"; +import {RuntimeArgs} from "../../../../contract-as/assembly/runtime_args"; +import {Pair} from "../../../../contract-as/assembly/pair"; + +const POS_ACTION = "unbond"; +const ARG_AMOUNT = "amount"; + +export function call(): void { + let proofOfStake = CL.getSystemContract(CL.SystemContract.ProofOfStake); + + let amountBytes = CL.getNamedArg(ARG_AMOUNT); + if (amountBytes === null) { + Error.fromErrorCode(ErrorCode.MissingArgument).revert(); + return; + } + + let amountResult = U512.fromBytes(amountBytes); + if (amountResult.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + let amount = amountResult.value; + let runtimeArgs = RuntimeArgs.fromArray([ + new Pair(ARG_AMOUNT, CLValue.fromU512(amount)), + ]); + CL.callContract(proofOfStake, POS_ACTION, runtimeArgs); +} diff --git a/smart-contracts/contracts-as/client/unbonding/assembly/tsconfig.json b/smart-contracts/contracts-as/client/unbonding/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/client/unbonding/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/client/unbonding/index.js b/smart-contracts/contracts-as/client/unbonding/index.js new file mode 100644 index 0000000000..a28cb9433d --- /dev/null +++ b/smart-contracts/contracts-as/client/unbonding/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/unbonding.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/client/unbonding/package.json b/smart-contracts/contracts-as/client/unbonding/package.json new file mode 100644 index 0000000000..4578eb3638 --- /dev/null +++ b/smart-contracts/contracts-as/client/unbonding/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/unbonding.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/add-update-associated-key/assembly/index.ts b/smart-contracts/contracts-as/test/add-update-associated-key/assembly/index.ts new file mode 100644 index 0000000000..32dfd60e85 --- /dev/null +++ b/smart-contracts/contracts-as/test/add-update-associated-key/assembly/index.ts @@ -0,0 +1,32 @@ +// The entry file of your WebAssembly module. +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {addAssociatedKey, AddKeyFailure, updateAssociatedKey, UpdateKeyFailure} from "../../../../contract-as/assembly/account"; +import {typedToArray} from "../../../../contract-as/assembly/utils"; +import {AccountHash} from "../../../../contract-as/assembly/key"; + + +const INIT_WEIGHT: u8 = 1; +const MOD_WEIGHT: u8 = 2; + +const ARG_ACCOUNT = "account"; + +export function call(): void { + let accountHashBytes = CL.getNamedArg(ARG_ACCOUNT); + const accountHashResult = AccountHash.fromBytes(accountHashBytes); + if (accountHashResult.hasError()) { + Error.fromUserError(4464 + accountHashResult.error).revert(); + return; + } + const accountHash = accountHashResult.value; + + if (addAssociatedKey(accountHash, INIT_WEIGHT) != AddKeyFailure.Ok) { + Error.fromUserError(4464).revert(); + return; + } + + if (updateAssociatedKey(accountHash, MOD_WEIGHT) != UpdateKeyFailure.Ok) { + Error.fromUserError(4464 + 1).revert(); + return; + } +} diff --git a/smart-contracts/contracts-as/test/add-update-associated-key/assembly/tsconfig.json b/smart-contracts/contracts-as/test/add-update-associated-key/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/add-update-associated-key/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/add-update-associated-key/index.js b/smart-contracts/contracts-as/test/add-update-associated-key/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/add-update-associated-key/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/add-update-associated-key/package.json b/smart-contracts/contracts-as/test/add-update-associated-key/package.json new file mode 100644 index 0000000000..6896674e2c --- /dev/null +++ b/smart-contracts/contracts-as/test/add-update-associated-key/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/add_update_associated_key.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/authorized-keys/assembly/index.ts b/smart-contracts/contracts-as/test/authorized-keys/assembly/index.ts new file mode 100644 index 0000000000..69d826c0bc --- /dev/null +++ b/smart-contracts/contracts-as/test/authorized-keys/assembly/index.ts @@ -0,0 +1,46 @@ +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {fromBytesString, fromBytesI32} from "../../../../contract-as/assembly/bytesrepr"; +import {arrayToTyped} from "../../../../contract-as/assembly/utils"; +import {Key, AccountHash} from "../../../../contract-as/assembly/key" +import {addAssociatedKey, AddKeyFailure, ActionType, setActionThreshold, SetThresholdFailure} from "../../../../contract-as/assembly/account"; + +const ARG_KEY_MANAGEMENT_THRESHOLD = "key_management_threshold"; +const ARG_DEPLOY_THRESHOLD = "deploy_threshold"; + +export function call(): void { + let publicKeyBytes = new Array(32); + publicKeyBytes.fill(123); + let accountHash = new AccountHash(arrayToTyped(publicKeyBytes)); + + const addResult = addAssociatedKey(accountHash, 100); + switch (addResult) { + case AddKeyFailure.DuplicateKey: + break; + case AddKeyFailure.Ok: + break; + default: + Error.fromUserError(50).revert(); + break; + } + + let keyManagementThresholdBytes = CL.getNamedArg(ARG_KEY_MANAGEMENT_THRESHOLD); + let keyManagementThreshold = keyManagementThresholdBytes[0]; + + let deployThresholdBytes = CL.getNamedArg(ARG_DEPLOY_THRESHOLD); + let deployThreshold = deployThresholdBytes[0]; + + if (keyManagementThreshold != 0) { + if (setActionThreshold(ActionType.KeyManagement, keyManagementThreshold) != SetThresholdFailure.Ok) { + // TODO: Create standard Error from those enum values + Error.fromUserError(4464 + 1).revert(); + } + } + if (deployThreshold != 0) { + if (setActionThreshold(ActionType.Deployment, deployThreshold) != SetThresholdFailure.Ok) { + Error.fromUserError(4464).revert(); + return; + } + } + +} diff --git a/smart-contracts/contracts-as/test/authorized-keys/assembly/tsconfig.json b/smart-contracts/contracts-as/test/authorized-keys/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/authorized-keys/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/authorized-keys/index.js b/smart-contracts/contracts-as/test/authorized-keys/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/authorized-keys/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/authorized-keys/package.json b/smart-contracts/contracts-as/test/authorized-keys/package.json new file mode 100644 index 0000000000..46f3cb0479 --- /dev/null +++ b/smart-contracts/contracts-as/test/authorized-keys/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/authorized_keys.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/create-purse-01/assembly/index.ts b/smart-contracts/contracts-as/test/create-purse-01/assembly/index.ts new file mode 100644 index 0000000000..cd0718eb90 --- /dev/null +++ b/smart-contracts/contracts-as/test/create-purse-01/assembly/index.ts @@ -0,0 +1,30 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; +import {Key} from "../../../../contract-as/assembly/key"; +import {putKey} from "../../../../contract-as/assembly"; +import {createPurse} from "../../../../contract-as/assembly/purse"; +import {URef} from "../../../../contract-as/assembly/uref"; + +const ARG_PURSE_NAME = "purse_name"; + +export function delegate(): void { + // purse name arg + const purseNameArg = CL.getNamedArg(ARG_PURSE_NAME); + const purseNameResult = fromBytesString(purseNameArg); + if (purseNameResult.hasError()) { + Error.fromErrorCode(ErrorCode.PurseNotCreated).revert(); + return; + } + let purseName = purseNameResult.value; + + const purse = createPurse(); + + const key = Key.fromURef(purse); + putKey(purseName, key); +} + +export function call(): void { + delegate(); +} diff --git a/smart-contracts/contracts-as/test/create-purse-01/assembly/tsconfig.json b/smart-contracts/contracts-as/test/create-purse-01/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/create-purse-01/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/create-purse-01/index.js b/smart-contracts/contracts-as/test/create-purse-01/index.js new file mode 100644 index 0000000000..eb1a7fac05 --- /dev/null +++ b/smart-contracts/contracts-as/test/create-purse-01/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/create_purse_01.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/create-purse-01/package.json b/smart-contracts/contracts-as/test/create-purse-01/package.json new file mode 100644 index 0000000000..4b76e52432 --- /dev/null +++ b/smart-contracts/contracts-as/test/create-purse-01/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/create_purse_01.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/index.ts b/smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/index.ts new file mode 100644 index 0000000000..822ef08d36 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/index.ts @@ -0,0 +1,26 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {fromBytesString, toBytesU32} from "../../../../contract-as/assembly/bytesrepr"; +import {CLValue} from "../../../../contract-as/assembly/clvalue"; +import {RuntimeArgs} from "../../../../contract-as/assembly/runtime_args"; +import {Pair} from "../../../../contract-as/assembly/pair"; +import {Option} from "../../../../contract-as/assembly/option"; +import {arrayToTyped} from "../../../../contract-as/assembly/utils"; + +const ENTRY_FUNCTION_NAME = "delegate"; +const PURSE_NAME_ARG_NAME = "purse_name"; +const ARG_CONTRACT_PACKAGE = "contract_package"; +const ARG_NEW_PURSE_NAME = "new_purse_name"; +const ARG_VERSION = "version"; + +export function call(): void { + let contractPackageHash = CL.getNamedArg(ARG_CONTRACT_PACKAGE); + const newPurseNameBytes = CL.getNamedArg(ARG_NEW_PURSE_NAME); + const newPurseName = fromBytesString(newPurseNameBytes).unwrap(); + const versionNumber = CL.getNamedArg(ARG_VERSION)[0]; + let contractVersion = new Option(arrayToTyped(toBytesU32(versionNumber))); + let runtimeArgs = RuntimeArgs.fromArray([ + new Pair(PURSE_NAME_ARG_NAME, CLValue.fromString(newPurseName)), + ]); + CL.callVersionedContract(contractPackageHash, contractVersion, ENTRY_FUNCTION_NAME, runtimeArgs); +} diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/tsconfig.json b/smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-caller/index.js b/smart-contracts/contracts-as/test/do-nothing-stored-caller/index.js new file mode 100644 index 0000000000..6d5c2dca18 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored-caller/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing_stored_caller.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-caller/package.json b/smart-contracts/contracts-as/test/do-nothing-stored-caller/package.json new file mode 100644 index 0000000000..620b28cda3 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored-caller/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/do_nothing_stored_caller.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/index.ts b/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/index.ts new file mode 100644 index 0000000000..7a6c811ed9 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/index.ts @@ -0,0 +1,55 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; +import {Key} from "../../../../contract-as/assembly/key"; +import {Pair} from "../../../../contract-as/assembly/pair"; +import {URef} from "../../../../contract-as/assembly/uref"; +import {Pair} from "../../../../contract-as/assembly/pair"; +import {createPurse} from "../../../../contract-as/assembly/purse"; +import {CLType, CLTypeTag} from "../../../../contract-as/assembly/clvalue"; +import * as CreatePurse01 from "../../create-purse-01/assembly"; + +const ENTRY_FUNCTION_NAME = "delegate"; +const DO_NOTHING_PACKAGE_HASH_KEY_NAME = "do_nothing_package_hash"; +const DO_NOTHING_ACCESS_KEY_NAME = "do_nothing_access"; + +export function delegate(): void { + let key = new Uint8Array(32); + for (var i = 0; i < 32; i++) { + key[i] = 1; + } + CL.putKey("called_do_nothing_ver_2", Key.fromHash(key)); + CreatePurse01.delegate(); +} + +export function call(): void { + let entryPoints = new CL.EntryPoints(); + let entryPoint = new CL.EntryPoint( + ENTRY_FUNCTION_NAME, + new Array>(), + new CLType(CLTypeTag.Unit), + new CL.PublicAccess(), + CL.EntryPointType.Session); + entryPoints.addEntryPoint(entryPoint); + + let doNothingPackageHash = CL.getKey(DO_NOTHING_PACKAGE_HASH_KEY_NAME); + if (doNothingPackageHash === null) { + Error.fromErrorCode(ErrorCode.None).revert(); + return; + } + + let doNothingURef = CL.getKey(DO_NOTHING_ACCESS_KEY_NAME); + if (doNothingURef === null) { + Error.fromErrorCode(ErrorCode.None).revert(); + return; + } + + const result = CL.addContractVersion( + doNothingPackageHash.hash, + entryPoints, + new Array>(), + ); + + CL.putKey("end of upgrade", Key.fromHash(result.contractHash)); +} diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/tsconfig.json b/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/index.js b/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/index.js new file mode 100644 index 0000000000..3572e101bf --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing_stored_upgrader.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/package.json b/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/package.json new file mode 100644 index 0000000000..3437cd988e --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/do_nothing_stored_upgrader.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/do-nothing-stored/assembly/index.ts b/smart-contracts/contracts-as/test/do-nothing-stored/assembly/index.ts new file mode 100644 index 0000000000..b3882fb6a4 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored/assembly/index.ts @@ -0,0 +1,36 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {fromBytesString, toBytesMap} from "../../../../contract-as/assembly/bytesrepr"; +import {Key} from "../../../../contract-as/assembly/key"; +import {CLValue, CLType, CLTypeTag} from "../../../../contract-as/assembly/clvalue"; +import {Pair} from "../../../../contract-as/assembly/pair"; + +const ENTRY_FUNCTION_NAME = "delegate"; +const HASH_KEY_NAME = "do_nothing_hash"; +const PACKAGE_HASH_KEY_NAME = "do_nothing_package_hash"; +const ACCESS_KEY_NAME = "do_nothing_access"; +const CONTRACT_VERSION = "contract_version"; + +export function delegate(): void { + // no-op +} + +export function call(): void { + let entryPoints = new CL.EntryPoints(); + let entryPoint = new CL.EntryPoint("delegate", new Array>(), new CLType(CLTypeTag.Unit), new CL.PublicAccess(), CL.EntryPointType.Contract); + entryPoints.addEntryPoint(entryPoint); + + const result = CL.newContract( + entryPoints, + null, + PACKAGE_HASH_KEY_NAME, + ACCESS_KEY_NAME, + ); + const key = Key.create(CLValue.fromI32(result.contractVersion)); + if (key === null) { + return; + } + CL.putKey(CONTRACT_VERSION, key); + CL.putKey(HASH_KEY_NAME, Key.fromHash(result.contractHash)); +} diff --git a/smart-contracts/contracts-as/test/do-nothing-stored/assembly/tsconfig.json b/smart-contracts/contracts-as/test/do-nothing-stored/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/do-nothing-stored/index.js b/smart-contracts/contracts-as/test/do-nothing-stored/index.js new file mode 100644 index 0000000000..83c6d5b5b2 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing_stored.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/do-nothing-stored/package.json b/smart-contracts/contracts-as/test/do-nothing-stored/package.json new file mode 100644 index 0000000000..b0c189bcd4 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing-stored/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/do_nothing_stored.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/do-nothing/assembly/index.ts b/smart-contracts/contracts-as/test/do-nothing/assembly/index.ts new file mode 100644 index 0000000000..829ecd7b16 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing/assembly/index.ts @@ -0,0 +1,5 @@ +// The entry file of your WebAssembly module. + +export function call(): void { + // This body intentionally left empty. +} diff --git a/smart-contracts/contracts-as/test/do-nothing/assembly/tsconfig.json b/smart-contracts/contracts-as/test/do-nothing/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/do-nothing/index.js b/smart-contracts/contracts-as/test/do-nothing/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/do-nothing/package.json b/smart-contracts/contracts-as/test/do-nothing/package.json new file mode 100644 index 0000000000..a9780e9b0f --- /dev/null +++ b/smart-contracts/contracts-as/test/do-nothing/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/do_nothing.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/endless-loop/assembly/index.ts b/smart-contracts/contracts-as/test/endless-loop/assembly/index.ts new file mode 100644 index 0000000000..5cbbc6f0ec --- /dev/null +++ b/smart-contracts/contracts-as/test/endless-loop/assembly/index.ts @@ -0,0 +1,7 @@ +import {getMainPurse} from "../../../../contract-as/assembly/account"; + +export function call(): void { + while(true){ + getMainPurse(); + } +} diff --git a/smart-contracts/contracts-as/test/endless-loop/assembly/tsconfig.json b/smart-contracts/contracts-as/test/endless-loop/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/endless-loop/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/endless-loop/index.js b/smart-contracts/contracts-as/test/endless-loop/index.js new file mode 100644 index 0000000000..d06677981f --- /dev/null +++ b/smart-contracts/contracts-as/test/endless-loop/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/endless_loop.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/endless-loop/package.json b/smart-contracts/contracts-as/test/endless-loop/package.json new file mode 100644 index 0000000000..f11f6a6f7a --- /dev/null +++ b/smart-contracts/contracts-as/test/endless-loop/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/endless_loop.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/get-arg/assembly/index.ts b/smart-contracts/contracts-as/test/get-arg/assembly/index.ts new file mode 100644 index 0000000000..827831fb70 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-arg/assembly/index.ts @@ -0,0 +1,36 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {U512} from "../../../../contract-as/assembly/bignum"; +import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; + +const EXPECTED_STRING = "Hello, world!"; +const EXPECTED_NUM = 42; + +const ARG_VALUE0 = "value0"; +const ARG_VALUE1 = "value1"; + +export function call(): void { + const stringArg = CL.getNamedArg(ARG_VALUE0); + const stringValResult = fromBytesString(stringArg) + if (stringValResult.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + let stringVal = stringValResult.value; + if (stringVal != EXPECTED_STRING){ + unreachable(); + return; + } + const u512Arg = CL.getNamedArg(ARG_VALUE1); + const u512ValResult = U512.fromBytes(u512Arg); + if (u512ValResult.hasError() || u512Arg.length > u512ValResult.position) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + let u512Val = u512ValResult.value; + if (u512Val != U512.fromU64(EXPECTED_NUM)){ + unreachable(); + return; + } +} diff --git a/smart-contracts/contracts-as/test/get-arg/assembly/tsconfig.json b/smart-contracts/contracts-as/test/get-arg/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-arg/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/get-arg/index.js b/smart-contracts/contracts-as/test/get-arg/index.js new file mode 100644 index 0000000000..ca8d04b519 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-arg/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/get_arg.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/get-arg/package.json b/smart-contracts/contracts-as/test/get-arg/package.json new file mode 100644 index 0000000000..2dd97ec23f --- /dev/null +++ b/smart-contracts/contracts-as/test/get-arg/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/get_arg.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/get-blocktime/assembly/index.ts b/smart-contracts/contracts-as/test/get-blocktime/assembly/index.ts new file mode 100644 index 0000000000..5ec1668b78 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-blocktime/assembly/index.ts @@ -0,0 +1,17 @@ +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {fromBytesU64} from "../../../../contract-as/assembly/bytesrepr"; + +const ARG_KNOWN_BLOCK_TIME = "known_block_time"; + +export function call(): void { + const knownBlockTimeBytes = CL.getNamedArg(ARG_KNOWN_BLOCK_TIME); + const knownBlockTime = fromBytesU64(knownBlockTimeBytes); + if (knownBlockTime.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + + const blockTime = CL.getBlockTime(); + assert(blockTime == knownBlockTime.value); +} diff --git a/smart-contracts/contracts-as/test/get-blocktime/assembly/tsconfig.json b/smart-contracts/contracts-as/test/get-blocktime/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-blocktime/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/get-blocktime/index.js b/smart-contracts/contracts-as/test/get-blocktime/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-blocktime/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/get-blocktime/package.json b/smart-contracts/contracts-as/test/get-blocktime/package.json new file mode 100644 index 0000000000..997cafd0d0 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-blocktime/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/get_blocktime.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/get-caller/assembly/index.ts b/smart-contracts/contracts-as/test/get-caller/assembly/index.ts new file mode 100644 index 0000000000..cea609365f --- /dev/null +++ b/smart-contracts/contracts-as/test/get-caller/assembly/index.ts @@ -0,0 +1,19 @@ +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {typedToArray, checkArraysEqual} from "../../../../contract-as/assembly/utils"; +import {AccountHash} from "../../../../contract-as/assembly/key"; + +const ARG_ACCOUNT = "account"; + +export function call(): void { + const knownAccountHashBytes = CL.getNamedArg(ARG_ACCOUNT); + let knownAccountHashResult = AccountHash.fromBytes(knownAccountHashBytes); + if (knownAccountHashResult.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + const knownAccountHash = knownAccountHashResult.value; + const caller = CL.getCaller(); + + assert(caller == knownAccountHash); +} diff --git a/smart-contracts/contracts-as/test/get-caller/assembly/tsconfig.json b/smart-contracts/contracts-as/test/get-caller/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-caller/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/get-caller/index.js b/smart-contracts/contracts-as/test/get-caller/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-caller/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/get-caller/package.json b/smart-contracts/contracts-as/test/get-caller/package.json new file mode 100644 index 0000000000..d1a9ae7dfb --- /dev/null +++ b/smart-contracts/contracts-as/test/get-caller/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/get_caller.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/get-phase-payment/assembly/index.ts b/smart-contracts/contracts-as/test/get-phase-payment/assembly/index.ts new file mode 100644 index 0000000000..3e102a2012 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-phase-payment/assembly/index.ts @@ -0,0 +1,52 @@ +// The entry file of your WebAssembly module. +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {U512} from "../../../../contract-as/assembly/bignum"; +import {URef} from "../../../../contract-as/assembly/uref"; +import {RuntimeArgs} from "../../../../contract-as/assembly/runtime_args"; +import {getMainPurse} from "../../../../contract-as/assembly/account"; +import {transferFromPurseToPurse} from "../../../../contract-as/assembly/purse"; + +const ARG_PHASE = "phase"; +const ARG_AMOUNT = "amount"; +const POS_ACTION = "get_payment_purse"; + +function standardPayment(amount: U512): void { + let proofOfStake = CL.getSystemContract(CL.SystemContract.ProofOfStake); + + let mainPurse = getMainPurse(); + + let output = CL.callContract(proofOfStake, POS_ACTION, new RuntimeArgs()); + + let paymentPurseResult = URef.fromBytes(output); + if (paymentPurseResult.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidPurse).revert(); + return; + } + let paymentPurse = paymentPurseResult.value; + + let ret = transferFromPurseToPurse( + mainPurse, + paymentPurse, + amount, + ); + if (ret > 0) { + Error.fromErrorCode(ErrorCode.Transfer).revert(); + return; + } +} + +export function call(): void { + const phaseBytes = CL.getNamedArg(ARG_PHASE); + if (phaseBytes.length != 1) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + + const phase = phaseBytes[0]; + + const caller = CL.getPhase(); + assert(phase == caller); + + standardPayment(U512.fromU64(10000000)); +} diff --git a/smart-contracts/contracts-as/test/get-phase-payment/assembly/tsconfig.json b/smart-contracts/contracts-as/test/get-phase-payment/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-phase-payment/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/get-phase-payment/index.js b/smart-contracts/contracts-as/test/get-phase-payment/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-phase-payment/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/get-phase-payment/package.json b/smart-contracts/contracts-as/test/get-phase-payment/package.json new file mode 100644 index 0000000000..aca27f67e1 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-phase-payment/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/get_phase_payment.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/get-phase/assembly/index.ts b/smart-contracts/contracts-as/test/get-phase/assembly/index.ts new file mode 100644 index 0000000000..37a58741ce --- /dev/null +++ b/smart-contracts/contracts-as/test/get-phase/assembly/index.ts @@ -0,0 +1,17 @@ +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; + +const ARG_PHASE = "phase"; + +export function call(): void { + const phaseBytes = CL.getNamedArg(ARG_PHASE); + if (phaseBytes.length != 1) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + + const phase = phaseBytes[0]; + + const caller = CL.getPhase(); + assert(phase == caller); +} diff --git a/smart-contracts/contracts-as/test/get-phase/assembly/tsconfig.json b/smart-contracts/contracts-as/test/get-phase/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-phase/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/get-phase/index.js b/smart-contracts/contracts-as/test/get-phase/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-phase/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/get-phase/package.json b/smart-contracts/contracts-as/test/get-phase/package.json new file mode 100644 index 0000000000..9213f8d144 --- /dev/null +++ b/smart-contracts/contracts-as/test/get-phase/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/get_phase.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/groups/assembly/index.ts b/smart-contracts/contracts-as/test/groups/assembly/index.ts new file mode 100644 index 0000000000..120761982b --- /dev/null +++ b/smart-contracts/contracts-as/test/groups/assembly/index.ts @@ -0,0 +1,241 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import { Error, ErrorCode } from "../../../../contract-as/assembly/error"; +import { Key } from "../../../../contract-as/assembly/key"; +import { URef } from "../../../../contract-as/assembly/uref"; +import { CLValue, CLType, CLTypeTag } from "../../../../contract-as/assembly/clvalue"; +import { Pair } from "../../../../contract-as/assembly/pair"; +import { RuntimeArgs } from "../../../../contract-as/assembly/runtime_args"; +import { Option } from "../../../../contract-as/assembly/option"; +import { toBytesU32 } from "../../../../contract-as/assembly/bytesrepr"; +import { arrayToTyped } from "../../../../contract-as/assembly/utils"; + +const CONTRACT_INITIAL_VERSION: u8 = 1; +const PACKAGE_HASH_KEY = "package_hash_key"; +const PACKAGE_ACCESS_KEY = "package_access_key"; +const RESTRICTED_CONTRACT = "restricted_contract"; +const RESTRICTED_SESSION = "restricted_session"; +const RESTRICTED_SESSION_CALLER = "restricted_session_caller"; +const UNRESTRICTED_CONTRACT_CALLER = "unrestricted_contract_caller"; +const RESTRICTED_CONTRACT_CALLER_AS_SESSION = "restricted_contract_caller_as_session"; +const UNCALLABLE_SESSION = "uncallable_session"; +const UNCALLABLE_CONTRACT = "uncallable_contract"; +const CALL_RESTRICTED_ENTRY_POINTS = "call_restricted_entry_points"; +const ARG_PACKAGE_HASH = "package_hash"; + +export function restricted_session(): void { } + +export function restricted_contract(): void { } + +export function restricted_session_caller(): void { + let packageHashBytes = CL.getNamedArg(ARG_PACKAGE_HASH); + let packageKey = Key.fromBytes(packageHashBytes).unwrap(); + let contractVersion = new Option(arrayToTyped(toBytesU32(CONTRACT_INITIAL_VERSION))); + CL.callVersionedContract( + packageKey.hash, + contractVersion, + RESTRICTED_SESSION, + new RuntimeArgs(), + ); +} + +function contract_caller(): void { + let packageHashBytes = CL.getNamedArg(ARG_PACKAGE_HASH); + let packageKey = Key.fromBytes(packageHashBytes).unwrap(); + let contractVersion = new Option(arrayToTyped(toBytesU32(CONTRACT_INITIAL_VERSION))); + CL.callVersionedContract( + packageKey.hash, + contractVersion, + RESTRICTED_CONTRACT, + new RuntimeArgs(), + ); +} + +export function unrestricted_contract_caller(): void { + contract_caller(); +} + +export function restricted_contract_caller_as_session(): void { + contract_caller(); +} + +export function uncallable_session(): void { } + +export function uncallable_contract(): void { } + +export function call_restricted_entry_points(): void { + // We're aggressively removing exports that aren't exposed through contract header so test + // ensures that those exports are still inside WASM. + uncallable_session(); + uncallable_contract(); +} + + +function createGroup(packageHash: Uint8Array): URef { + let key = Key.create(CLValue.fromU64(0)); + if (key === null) { + Error.fromErrorCode(ErrorCode.Formatting).revert(); + return unreachable(); + } + + CL.putKey("saved_uref", key); + + let existingURefs: Array = [key.uref]; + + let newURefs = CL.createContractUserGroup( + packageHash, + "Group 1", + 1, + existingURefs, + ); + + if (newURefs.length != 1) { + Error.fromUserError(4464 + 1000 + 1).revert(); + return unreachable(); + } + return newURefs[0]; +} + +function createEntryPoints(): CL.EntryPoints { + let entryPoints = new CL.EntryPoints(); + let restrictedSession = new CL.EntryPoint( + RESTRICTED_SESSION, + new Array>(), + new CLType(CLTypeTag.Unit), + new CL.GroupAccess(["Group 1"]), + CL.EntryPointType.Session, + ); + entryPoints.addEntryPoint(restrictedSession); + + let restricted_contract = new CL.EntryPoint( + RESTRICTED_CONTRACT, + new Array>(), + new CLType(CLTypeTag.Unit), + new CL.GroupAccess(["Group 1"]), + CL.EntryPointType.Contract, + ); + entryPoints.addEntryPoint(restricted_contract); + + let restrictedSessionCallerParams = new Array>(); + restrictedSessionCallerParams.push(new Pair(ARG_PACKAGE_HASH, new CLType(CLTypeTag.Key))); + let restricted_session_caller = new CL.EntryPoint( + RESTRICTED_SESSION_CALLER, + restrictedSessionCallerParams, + new CLType(CLTypeTag.Unit), + new CL.PublicAccess(), + CL.EntryPointType.Session, + ); + entryPoints.addEntryPoint(restricted_session_caller); + + let restricted_contract2 = new CL.EntryPoint( + RESTRICTED_CONTRACT, + new Array>(), + new CLType(CLTypeTag.Unit), + new CL.GroupAccess(["Group 1"]), + CL.EntryPointType.Contract, + ); + entryPoints.addEntryPoint(restricted_contract2); + + let unrestricted_contract_caller = new CL.EntryPoint( + UNRESTRICTED_CONTRACT_CALLER, + new Array>(), + new CLType(CLTypeTag.Unit), + // Made public because we've tested deploy level auth into a contract in + // RESTRICTED_CONTRACT entrypoint + new CL.PublicAccess(), + // NOTE: Public contract authorizes any contract call, because this contract has groups + // uref in its named keys + CL.EntryPointType.Contract, + ); + entryPoints.addEntryPoint(unrestricted_contract_caller); + + let unrestricted_contract_caller_as_session = new CL.EntryPoint( + RESTRICTED_CONTRACT_CALLER_AS_SESSION, + new Array>(), + new CLType(CLTypeTag.Unit), + // Made public because we've tested deploy level auth into a contract in + // RESTRICTED_CONTRACT entrypoint + new CL.PublicAccess(), + // NOTE: Public contract authorizes any contract call, because this contract has groups + // uref in its named keys + CL.EntryPointType.Session, + ); + entryPoints.addEntryPoint(unrestricted_contract_caller_as_session); + + let uncallable_session = new CL.EntryPoint( + UNCALLABLE_SESSION, + new Array>(), + new CLType(CLTypeTag.Unit), + // Made public because we've tested deploy level auth into a contract in + // RESTRICTED_CONTRACT entrypoint + new CL.GroupAccess([]), + // NOTE: Public contract authorizes any contract call, because this contract has groups + // uref in its named keys + CL.EntryPointType.Session, + ); + entryPoints.addEntryPoint(uncallable_session); + + let uncallable_contract = new CL.EntryPoint( + UNCALLABLE_CONTRACT, + new Array>(), + new CLType(CLTypeTag.Unit), + // Made public because we've tested deploy level auth into a contract in + // RESTRICTED_CONTRACT entrypoint + new CL.GroupAccess([]), + // NOTE: Public contract authorizes any contract call, because this contract has groups + // uref in its named keys + CL.EntryPointType.Session, + ); + entryPoints.addEntryPoint(uncallable_contract); + + // Directly calls entryPoints that are protected with empty group of lists to verify that even + // though they're not callable externally, they're still visible in the WASM. + let call_restricted_entry_points = new CL.EntryPoint( + CALL_RESTRICTED_ENTRY_POINTS, + new Array>(), + new CLType(CLTypeTag.Unit), + // Made public because we've tested deploy level auth into a contract in + // RESTRICTED_CONTRACT entrypoint + new CL.PublicAccess(), + // NOTE: Public contract authorizes any contract call, because this contract has groups + // uref in its named keys + CL.EntryPointType.Session, + ); + entryPoints.addEntryPoint(call_restricted_entry_points); + + return entryPoints; +} + +function installVersion1( + contractPackageHash: Uint8Array, + restrictedURef: URef, +): void { + let contractVariable = Key.create(CLValue.fromI32(0)); + if (contractVariable === null) { + Error.fromErrorCode(ErrorCode.Formatting).revert(); + unreachable(); + return; + } + + let namedKeys = new Array>(); + namedKeys.push(new Pair("contract_named_key", contractVariable)); + namedKeys.push(new Pair("restricted_uref", Key.fromURef(restrictedURef))); + + let entryPoints = createEntryPoints(); + + const result = CL.addContractVersion( + contractPackageHash, + entryPoints, + namedKeys, + ); +} + + +export function call(): void { + let createResult = CL.createContractPackageAtHash(); + CL.putKey(PACKAGE_HASH_KEY, Key.fromHash(createResult.packageHash)); + CL.putKey(PACKAGE_ACCESS_KEY, Key.fromURef(createResult.accessURef)); + + let restrictedURef = createGroup(createResult.packageHash); + installVersion1(createResult.packageHash, restrictedURef); +} diff --git a/smart-contracts/contracts-as/test/groups/assembly/tsconfig.json b/smart-contracts/contracts-as/test/groups/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/groups/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/groups/index.js b/smart-contracts/contracts-as/test/groups/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/groups/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/groups/package.json b/smart-contracts/contracts-as/test/groups/package.json new file mode 100644 index 0000000000..b7625c1c89 --- /dev/null +++ b/smart-contracts/contracts-as/test/groups/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/groups.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/key-management-thresholds/assembly/index.ts b/smart-contracts/contracts-as/test/key-management-thresholds/assembly/index.ts new file mode 100644 index 0000000000..831c4fa686 --- /dev/null +++ b/smart-contracts/contracts-as/test/key-management-thresholds/assembly/index.ts @@ -0,0 +1,135 @@ +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; +import {arrayToTyped} from "../../../../contract-as/assembly/utils"; +import {AccountHash} from "../../../../contract-as/assembly/key"; +import {addAssociatedKey, AddKeyFailure, + setActionThreshold, ActionType, SetThresholdFailure, + updateAssociatedKey, UpdateKeyFailure, + removeAssociatedKey, RemoveKeyFailure} from "../../../../contract-as/assembly/account"; + +const ARG_STAGE = "stage"; + +export function call(): void { + let stageBytes = CL.getNamedArg(ARG_STAGE); + let stageResult = fromBytesString(stageBytes); + if (stageResult.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + let stage = stageResult.value; + + let key42sBytes = new Array(32); + key42sBytes.fill(42); + let key42s = new AccountHash(arrayToTyped(key42sBytes)); + + let key43sBytes = new Array(32); + key43sBytes.fill(43); + let key43s = new AccountHash(arrayToTyped(key43sBytes)); + + let key1sBytes = new Array(32); + key1sBytes.fill(1); + let key1s = new AccountHash(arrayToTyped(key1sBytes)); + + if (stage == "init") { + if (addAssociatedKey(key42s, 100) != AddKeyFailure.Ok) { + Error.fromUserError(4464).revert(); + return; + } + if (addAssociatedKey(key43s, 1) != AddKeyFailure.Ok) { + Error.fromUserError(4464 + 1).revert(); + return; + } + if (addAssociatedKey(key1s, 1) != AddKeyFailure.Ok) { + Error.fromUserError(4464 + 2).revert(); + return; + } + + if (setActionThreshold(ActionType.KeyManagement, 101) != SetThresholdFailure.Ok) { + Error.fromUserError(4464 + 3).revert(); + return; + } + } + else if (stage == "test-permission-denied") { + let key44sBytes = new Array(32); + key44sBytes.fill(44); + let key44s = new AccountHash(arrayToTyped(key44sBytes)); + switch (addAssociatedKey(key44s, 1)) { + case AddKeyFailure.Ok: + Error.fromUserError(200).revert(); + break; + case AddKeyFailure.PermissionDenied: + break; + default: + Error.fromUserError(201).revert(); + break; + } + + let key43sBytes = new Array(32); + key43sBytes.fill(43); + let key43s = new AccountHash(arrayToTyped(key43sBytes)); + + switch (updateAssociatedKey(key43s, 2)) { + case UpdateKeyFailure.Ok: + Error.fromUserError(300).revert(); + break; + case UpdateKeyFailure.PermissionDenied: + break; + default: + Error.fromUserError(301).revert(); + break; + } + + switch (removeAssociatedKey(key43s)) { + case RemoveKeyFailure.Ok: + Error.fromUserError(400).revert(); + break; + case RemoveKeyFailure.PermissionDenied: + break; + default: + Error.fromUserError(401).revert(); + break; + } + + switch (setActionThreshold(ActionType.KeyManagement, 255)) { + case SetThresholdFailure.Ok: + Error.fromUserError(500).revert(); + break; + case SetThresholdFailure.PermissionDeniedError: + break; + default: + Error.fromUserError(501).revert(); + break; + } + } + else if (stage == "test-key-mgmnt-succeed") { + let key44sBytes = new Array(32); + key44sBytes.fill(44); + let key44s = new AccountHash(arrayToTyped(key44sBytes)); + + // Has to be executed with keys of total weight >= 254 + if (addAssociatedKey(key44s, 1) != AddKeyFailure.Ok) { + Error.fromUserError(4464 + 4).revert(); + return; + } + + // Updates [43;32] key weight created in init stage + if (updateAssociatedKey(key44s, 2) != UpdateKeyFailure.Ok) { + Error.fromUserError(4464 + 5).revert(); + return; + } + // Removes [43;32] key created in init stage + if (removeAssociatedKey(key44s) != RemoveKeyFailure.Ok) { + Error.fromUserError(4464 + 6).revert(); + return; + } + // Sets action threshodl + if (setActionThreshold(ActionType.KeyManagement, 100) != SetThresholdFailure.Ok) { + Error.fromUserError(4464 + 7).revert(); + return; + } + } + else { + Error.fromUserError(1).revert(); + } +} diff --git a/smart-contracts/contracts-as/test/key-management-thresholds/assembly/tsconfig.json b/smart-contracts/contracts-as/test/key-management-thresholds/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/key-management-thresholds/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/key-management-thresholds/index.js b/smart-contracts/contracts-as/test/key-management-thresholds/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/key-management-thresholds/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/key-management-thresholds/package.json b/smart-contracts/contracts-as/test/key-management-thresholds/package.json new file mode 100644 index 0000000000..2b19cab0d1 --- /dev/null +++ b/smart-contracts/contracts-as/test/key-management-thresholds/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/key_management_thresholds.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/list-named-keys/assembly/index.ts b/smart-contracts/contracts-as/test/list-named-keys/assembly/index.ts new file mode 100644 index 0000000000..982ba47d0a --- /dev/null +++ b/smart-contracts/contracts-as/test/list-named-keys/assembly/index.ts @@ -0,0 +1,94 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {fromBytesMap, fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; +import {Key} from "../../../../contract-as/assembly/key"; +import {checkItemsEqual} from "../../../../contract-as/assembly/utils"; + +const ARG_INITIAL_NAMED_KEYS = "initial_named_args"; +const ARG_NEW_NAMED_KEYS = "new_named_keys"; + +enum CustomError { + MissingInitialNamedKeys = 0, + InvalidInitialNamedKeys = 1, + MissingNewNamedKeys = 2, + InvalidNewNamedKeys = 3, + MissingActualNamedKeys = 4464, + MismatchedKeys = 4505, +} + +export function call(): void { + let expectedInitialNamedKeysBytes = CL.getNamedArg(ARG_INITIAL_NAMED_KEYS); + + const mapResult = fromBytesMap( + expectedInitialNamedKeysBytes, + fromBytesString, + Key.fromBytes + ); + if (mapResult.hasError()) { + Error.fromUserError(CustomError.InvalidInitialNamedKeys).revert(); + return; + } + let expectedInitialNamedKeys = mapResult.value; + + + let actualNamedKeys = CL.listNamedKeys(); + if (actualNamedKeys === null) { + Error.fromUserError(CustomError.MissingActualNamedKeys).revert(); + return; + } + + + if (!checkItemsEqual(expectedInitialNamedKeys, actualNamedKeys)) { + Error.fromUserError(CustomError.MismatchedKeys).revert(); + return; + } + + let newNamedKeysBytes = CL.getNamedArg(ARG_NEW_NAMED_KEYS); + const mapResult2 = fromBytesMap( + newNamedKeysBytes, + fromBytesString, + Key.fromBytes + ); + if (mapResult2.hasError()) { + Error.fromUserError(CustomError.InvalidNewNamedKeys).revert(); + return; + } + let newNamedKeys = mapResult2.value; + + let expectedNamedKeys = expectedInitialNamedKeys; + + for (let i = 0; i < newNamedKeys.length; i++) { + const namedKey = newNamedKeys[i]; + CL.putKey(namedKey.first, namedKey.second); + expectedNamedKeys.push(namedKey); + + const actualNamedKeys = CL.listNamedKeys(); + assert(checkItemsEqual(expectedNamedKeys, actualNamedKeys)); + } + + + let allKeyNames = new Array(); + for (let i = 0; i < expectedNamedKeys.length; i++) { + allKeyNames.push(expectedNamedKeys[i].first); + } + + for (let i = 0; i < allKeyNames.length; i++) { + CL.removeKey(allKeyNames[i]); + + // TODO: remove on an ordered map, or reconsider giving Map a try with Map.remove + let removed = false; + for (let j = 0; j < expectedNamedKeys.length; j++) { + if (expectedNamedKeys[j].first == allKeyNames[i]) { + expectedNamedKeys.splice(j, 1); + removed = true; + break; + } + } + + assert(removed); + + const actualNamedKeys = CL.listNamedKeys(); + assert(checkItemsEqual(expectedNamedKeys, actualNamedKeys)); + } +} diff --git a/smart-contracts/contracts-as/test/list-named-keys/assembly/tsconfig.json b/smart-contracts/contracts-as/test/list-named-keys/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/list-named-keys/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/list-named-keys/index.js b/smart-contracts/contracts-as/test/list-named-keys/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/list-named-keys/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/list-named-keys/package.json b/smart-contracts/contracts-as/test/list-named-keys/package.json new file mode 100644 index 0000000000..f269ea1f40 --- /dev/null +++ b/smart-contracts/contracts-as/test/list-named-keys/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/list_named_keys.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/main-purse/assembly/index.ts b/smart-contracts/contracts-as/test/main-purse/assembly/index.ts new file mode 100644 index 0000000000..e970780d9e --- /dev/null +++ b/smart-contracts/contracts-as/test/main-purse/assembly/index.ts @@ -0,0 +1,27 @@ +//@ts-nocheck +import {getMainPurse} from "../../../../contract-as/assembly/account"; +import * as CL from "../../../../contract-as/assembly"; +import {Error} from "../../../../contract-as/assembly/error"; +import {URef} from "../../../../contract-as/assembly/uref"; + +const ARG_PURSE = "purse"; + +enum CustomError { + MissingExpectedMainPurseArg = 86, + InvalidExpectedMainPurseArg = 97, + EqualityAssertionFailed = 139 +} + +export function call(): void { + let expectedMainPurseArg = CL.getNamedArg(ARG_PURSE); + let purseResult = URef.fromBytes(expectedMainPurseArg); + if (purseResult === null){ + Error.fromUserError(CustomError.InvalidExpectedMainPurseArg).revert(); + return; + } + const expectedMainPurse = purseResult.value; + const actualMainPurse = getMainPurse(); + + if (expectedMainPurse != actualMainPurse) + Error.fromUserError(CustomError.EqualityAssertionFailed).revert(); +} diff --git a/smart-contracts/contracts-as/test/main-purse/assembly/tsconfig.json b/smart-contracts/contracts-as/test/main-purse/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/main-purse/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/main-purse/index.js b/smart-contracts/contracts-as/test/main-purse/index.js new file mode 100644 index 0000000000..0dd53f04a1 --- /dev/null +++ b/smart-contracts/contracts-as/test/main-purse/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/main_purse.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/main-purse/package.json b/smart-contracts/contracts-as/test/main-purse/package.json new file mode 100644 index 0000000000..197f7c50a8 --- /dev/null +++ b/smart-contracts/contracts-as/test/main-purse/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/main_purse.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/manage-groups/assembly/index.ts b/smart-contracts/contracts-as/test/manage-groups/assembly/index.ts new file mode 100644 index 0000000000..21ad0c2495 --- /dev/null +++ b/smart-contracts/contracts-as/test/manage-groups/assembly/index.ts @@ -0,0 +1,189 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import { Error, ErrorCode } from "../../../../contract-as/assembly/error"; +import { Key } from "../../../../contract-as/assembly/key"; +import { URef } from "../../../../contract-as/assembly/uref"; +import { fromBytesString, fromBytesU64, Result, fromBytesArray } from "../../../../contract-as/assembly/bytesrepr"; +import { CLValue, CLType, CLTypeTag } from "../../../../contract-as/assembly/clvalue"; +import { Pair } from "../../../../contract-as/assembly/pair"; +import { RuntimeArgs } from "../../../../contract-as/assembly/runtime_args"; + +const PACKAGE_HASH_KEY = "package_hash_key"; +const PACKAGE_ACCESS_KEY = "package_access_key"; +const CREATE_GROUP = "create_group"; +const REMOVE_GROUP = "remove_group"; +const EXTEND_GROUP_UREFS = "extend_group_urefs"; +const REMOVE_GROUP_UREFS = "remove_group_urefs"; +const GROUP_NAME_ARG = "group_name"; +const UREFS_ARG = "urefs"; +const TOTAL_NEW_UREFS_ARG = "total_new_urefs"; +const TOTAL_EXISTING_UREFS_ARG = "total_existing_urefs"; + +export function create_group(): void { + let packageHashKey = CL.getKey(PACKAGE_HASH_KEY); + if (packageHashKey === null) { + Error.fromErrorCode(ErrorCode.GetKey).revert(); + return; + } + let packageAccessKey = CL.getKey(PACKAGE_ACCESS_KEY); + if (packageAccessKey === null) { + Error.fromErrorCode(ErrorCode.GetKey).revert(); + return; + } + let group_name: String = fromBytesString(CL.getNamedArg(GROUP_NAME_ARG)).unwrap(); + let total_urefs: u64 = fromBytesU64(CL.getNamedArg(TOTAL_NEW_UREFS_ARG)).unwrap(); + let total_existing_urefs: u64 = fromBytesU64(CL.getNamedArg(TOTAL_EXISTING_UREFS_ARG)).unwrap(); + + let existingURefs = new Array(); + for (var i: u64 = 0; i < total_existing_urefs; i++) { + let res = Key.create(CLValue.fromU64(i)); + if (res === null) { + Error.fromErrorCode(ErrorCode.Formatting).revert(); + unreachable(); + return; + } + existingURefs.push(res.uref); + } + + let newURefs = CL.createContractUserGroup( + packageHashKey.hash, + group_name, + total_urefs as u8, + existingURefs, + ); +} + +export function remove_group(): void { + let packageHashKey = CL.getKey(PACKAGE_HASH_KEY); + if (packageHashKey === null) { + Error.fromErrorCode(ErrorCode.GetKey).revert(); + return; + } + let groupName: String = fromBytesString(CL.getNamedArg(GROUP_NAME_ARG)).unwrap(); + CL.removeContractUserGroup( + packageHashKey.hash, + groupName); +} + +export function extend_group_urefs(): void { + let packageHashKey = CL.getKey(PACKAGE_HASH_KEY); + if (packageHashKey === null) { + Error.fromErrorCode(ErrorCode.GetKey).revert(); + return; + } + let packageAccessKey = CL.getKey(PACKAGE_ACCESS_KEY); + if (packageAccessKey === null) { + Error.fromErrorCode(ErrorCode.GetKey).revert(); + return; + } + let groupName: String = fromBytesString(CL.getNamedArg(GROUP_NAME_ARG)).unwrap(); + let newURefsCount: u64 = fromBytesU64(CL.getNamedArg(TOTAL_NEW_UREFS_ARG)).unwrap(); + + // Creates 1 additional uref inside group + for (var i = 0; i < newURefsCount; i++) { + let _newURef = CL.extendContractUserGroupURefs( + packageHashKey.hash, + groupName + ); + } +} + +export function remove_group_urefs(): void { + let packageHashKey = CL.getKey(PACKAGE_HASH_KEY); + if (packageHashKey === null) { + Error.fromErrorCode(ErrorCode.GetKey).revert(); + return; + } + let groupName: String = fromBytesString(CL.getNamedArg(GROUP_NAME_ARG)).unwrap(); + let urefsBytes = CL.getNamedArg(UREFS_ARG); + let decode = function (bytes: Uint8Array): Result { + return URef.fromBytes(bytes); + }; + let urefs: Array = fromBytesArray(urefsBytes, decode).unwrap(); + + CL.removeContractUserGroupURefs( + packageHashKey.hash, + groupName, + urefs, + ); +} + +/// Restricted uref comes from creating a group and will be assigned to a smart contract +function createEntryPoints1(): CL.EntryPoints { + let entryPoints = new CL.EntryPoints(); + + { + let restrictedSessionParams = new Array>(); + restrictedSessionParams.push(new Pair(GROUP_NAME_ARG, new CLType(CLTypeTag.String))); + restrictedSessionParams.push(new Pair(TOTAL_EXISTING_UREFS_ARG, new CLType(CLTypeTag.U64))); + restrictedSessionParams.push(new Pair(TOTAL_NEW_UREFS_ARG, new CLType(CLTypeTag.U64))); + let restrictedSession = new CL.EntryPoint( + CREATE_GROUP, + restrictedSessionParams, + new CLType(CLTypeTag.Unit), + new CL.PublicAccess(), + CL.EntryPointType.Session, + ); + entryPoints.addEntryPoint(restrictedSession); + } + + { + let params = new Array>(); + params.push(new Pair(GROUP_NAME_ARG, new CLType(CLTypeTag.String))); + + let removeGroup = new CL.EntryPoint( + REMOVE_GROUP, + params, + new CLType(CLTypeTag.Unit), + new CL.PublicAccess(), + CL.EntryPointType.Session, + ); + entryPoints.addEntryPoint(removeGroup); + } + + { + let params = new Array>(); + params.push(new Pair(GROUP_NAME_ARG, new CLType(CLTypeTag.String))); + params.push(new Pair(TOTAL_NEW_UREFS_ARG, new CLType(CLTypeTag.U64))); + let extendGroupURefs = new CL.EntryPoint( + EXTEND_GROUP_UREFS, + params, + new CLType(CLTypeTag.Unit), + new CL.PublicAccess(), + CL.EntryPointType.Session, + ); + entryPoints.addEntryPoint(extendGroupURefs); + } + + { + let params = new Array>(); + params.push(new Pair(GROUP_NAME_ARG, new CLType(CLTypeTag.String))); + params.push(new Pair(UREFS_ARG, CLType.list(new CLType(CLTypeTag.Uref)))); + + let entry_point_name2 = REMOVE_GROUP_UREFS; + let remove_group_urefs = new CL.EntryPoint( + entry_point_name2, + params, + new CLType(CLTypeTag.Unit), + new CL.PublicAccess(), + CL.EntryPointType.Session, + ); + entryPoints.addEntryPoint(remove_group_urefs); + } + return entryPoints; +} + +function installVersion1(package_hash: Uint8Array): void { + let contractNamedKeys = new Array>(); + let entryPoints = createEntryPoints1(); + const result = CL.addContractVersion(package_hash, entryPoints, contractNamedKeys); +} + +export function call(): void { + let result = CL.createContractPackageAtHash(); + + CL.putKey(PACKAGE_HASH_KEY, Key.fromHash(result.packageHash)); + CL.putKey(PACKAGE_ACCESS_KEY, Key.fromURef(result.accessURef)); + + installVersion1(result.packageHash); +} diff --git a/smart-contracts/contracts-as/test/manage-groups/assembly/tsconfig.json b/smart-contracts/contracts-as/test/manage-groups/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/manage-groups/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/manage-groups/index.js b/smart-contracts/contracts-as/test/manage-groups/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/manage-groups/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/manage-groups/package.json b/smart-contracts/contracts-as/test/manage-groups/package.json new file mode 100644 index 0000000000..60b4f8c9f1 --- /dev/null +++ b/smart-contracts/contracts-as/test/manage-groups/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/manage_groups.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/named-keys/assembly/index.ts b/smart-contracts/contracts-as/test/named-keys/assembly/index.ts new file mode 100644 index 0000000000..bf65f5b3df --- /dev/null +++ b/smart-contracts/contracts-as/test/named-keys/assembly/index.ts @@ -0,0 +1,181 @@ +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {Key} from "../../../../contract-as/assembly/key"; +import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; +import {U512} from "../../../../contract-as/assembly/bignum"; +import {CLValue} from "../../../../contract-as/assembly/clvalue"; + +const COMMAND_CREATE_UREF1 = "create-uref1"; +const COMMAND_CREATE_UREF2 = "create-uref2"; +const COMMAND_REMOVE_UREF1 = "remove-uref1"; +const COMMAND_REMOVE_UREF2 = "remove-uref2"; +const COMMAND_TEST_READ_UREF1 = "test-read-uref1"; +const COMMAND_TEST_READ_UREF2 = "test-read-uref2"; +const COMMAND_INCREASE_UREF2 = "increase-uref2"; +const COMMAND_OVERWRITE_UREF2 = "overwrite-uref2"; +const ARG_COMMAND = "command"; + +export function call(): void { + let commandBytes = CL.getNamedArg(ARG_COMMAND); + let commandResult = fromBytesString(commandBytes); + if (commandResult.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidArgument); + return; + } + let command = commandResult.value; + + if (command == COMMAND_CREATE_UREF1) { + let helloWorldKey = Key.create(CLValue.fromString("Hello, world!")); + if (helloWorldKey === null) { + Error.fromUserError(4464 + 1).revert(); + return; + } + CL.putKey("hello-world", helloWorldKey); + } + + else if (command == COMMAND_CREATE_UREF2) { + let newBigValueKey = Key.create(CLValue.fromU512(U512.MAX_VALUE)); + if (newBigValueKey === null) { + Error.fromUserError(4464 + 4).revert(); + return; + } + CL.putKey("big-value", newBigValueKey); + } + + else if (command == COMMAND_REMOVE_UREF1) { + CL.removeKey("hello-world"); + } + + else if (command == COMMAND_REMOVE_UREF2) { + CL.removeKey("big-value"); + } + + else if (command == COMMAND_TEST_READ_UREF1) { + let namedKeys = CL.listNamedKeys(); + // Read data hidden behind `URef1` uref + namedKeys = CL.listNamedKeys(); + + let helloWorld: String = ""; + for (let i = 0; i < namedKeys.length; i++) { + if (namedKeys[i].first == "hello-world") { + let bytes = namedKeys[i].second.read(); + if (bytes === null) { + Error.fromUserError(4464 + 1000 + i).revert(); + return; + } + + let bytesString = fromBytesString(bytes); + if (bytesString.hasError()) { + Error.fromUserError(4464 + 2000 + i).revert(); + return; + } + helloWorld = bytesString.value; + } + } + + if (helloWorld != "Hello, world!") { + Error.fromUserError(4464 + 6).revert(); + return; + } + + // Read data through dedicated FFI function + let uref1 = CL.getKey("hello-world"); + if (uref1 === null) { + Error.fromErrorCode(ErrorCode.GetKey).revert(); + return; + } + let uref1Bytes = uref1.read(); + if (uref1Bytes === null) { + Error.fromUserError(4464 + 7).revert(); + return; + } + let uref1Str = fromBytesString(uref1Bytes); + if (uref1Str.hasError()) { + Error.fromUserError(4464 + 8).revert(); + return; + } + if (uref1Str.value != "Hello, world!") { + Error.fromUserError(4464 + 9).revert(); + return; + } + } + + else if (command == COMMAND_TEST_READ_UREF2) { + // Get the big value back + let bigValueKey = CL.getKey("big-value"); + if (bigValueKey === null) { + Error.fromErrorCode(ErrorCode.GetKey).revert(); + return; + } + let bigValueBytes = bigValueKey.read(); + if (bigValueBytes === null) { + Error.fromUserError(4464 + 12).revert(); + return; + } + let bigValue = U512.fromBytes(bigValueBytes); + if (bigValue.hasError()) { + Error.fromUserError(4464 + 13).revert(); + return; + } + + if (bigValue.value != U512.MAX_VALUE) { + Error.fromUserError(4464 + 14).revert(); + return; + } + } + + else if (command == COMMAND_INCREASE_UREF2) { + // Get the big value back + let bigValueKey = CL.getKey("big-value"); + if (bigValueKey === null) { + Error.fromErrorCode(ErrorCode.GetKey).revert(); + return; + } + // Increase by 1 + bigValueKey.add(CLValue.fromU512(U512.fromU64(1))); + let newBigValueBytes = bigValueKey.read(); + if (newBigValueBytes === null) { + Error.fromUserError(4464 + 15).revert(); + return; + } + let newBigValue = U512.fromBytes(newBigValueBytes); + if (newBigValue.hasError()) { + Error.fromUserError(4464 + 16).revert(); + return; + } + if (newBigValue.value != U512.MIN_VALUE) { + Error.fromUserError(4464 + 17).revert(); + return; + } + } + + else if (command == COMMAND_OVERWRITE_UREF2) { + // Get the big value back + let bigValueKey = CL.getKey("big-value"); + if (bigValueKey === null) { + Error.fromErrorCode(ErrorCode.GetKey).revert(); + return; + } + // I can overwrite some data under the pointer + bigValueKey.write(CLValue.fromU512(U512.fromU64(123456789))); + + let newBigValueBytes = bigValueKey.read(); + if (newBigValueBytes === null) { + Error.fromUserError(4464 + 18).revert(); + return; + } + let newBigValue = U512.fromBytes(newBigValueBytes); + if (newBigValue.hasError()) { + Error.fromUserError(4464 + 19).revert(); + return; + } + if (newBigValue.value != U512.fromU64(123456789)) { + Error.fromUserError(4464 + 20).revert(); + return; + } + } + + else { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + } +} diff --git a/smart-contracts/contracts-as/test/named-keys/assembly/tsconfig.json b/smart-contracts/contracts-as/test/named-keys/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/named-keys/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/named-keys/index.js b/smart-contracts/contracts-as/test/named-keys/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/named-keys/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/named-keys/package.json b/smart-contracts/contracts-as/test/named-keys/package.json new file mode 100644 index 0000000000..5494c8b8ea --- /dev/null +++ b/smart-contracts/contracts-as/test/named-keys/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/named_keys.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/overwrite-uref-content/assembly/index.ts b/smart-contracts/contracts-as/test/overwrite-uref-content/assembly/index.ts new file mode 100644 index 0000000000..ecb59b116d --- /dev/null +++ b/smart-contracts/contracts-as/test/overwrite-uref-content/assembly/index.ts @@ -0,0 +1,34 @@ +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {AccessRights, URef} from "../../../../contract-as/assembly/uref"; +import {Key} from "../../../../contract-as/assembly/key"; +import {CLValue} from "../../../../contract-as/assembly/clvalue"; + +const ARG_CONTRACT_UREF = "contract_uref"; +const REPLACEMENT_DATA = "bawitdaba"; + +export function call(): void { + let urefBytes = CL.getNamedArg(ARG_CONTRACT_UREF); + let urefResult = URef.fromBytes(urefBytes); + if (urefResult.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + let uref = urefResult.value; + + if (uref.isValid() == false){ + Error.fromUserError(1).revert(); + return; + } + + let elevatedUref = new URef( + uref.getBytes(), + AccessRights.READ_ADD_WRITE + ); + + let forgedKey = Key.fromURef(elevatedUref); + + let value = CLValue.fromString(REPLACEMENT_DATA); + + forgedKey.write(value); +} diff --git a/smart-contracts/contracts-as/test/overwrite-uref-content/assembly/tsconfig.json b/smart-contracts/contracts-as/test/overwrite-uref-content/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/overwrite-uref-content/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/overwrite-uref-content/index.js b/smart-contracts/contracts-as/test/overwrite-uref-content/index.js new file mode 100644 index 0000000000..d88d7abc84 --- /dev/null +++ b/smart-contracts/contracts-as/test/overwrite-uref-content/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/overwrite_uref_content.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/overwrite-uref-content/package.json b/smart-contracts/contracts-as/test/overwrite-uref-content/package.json new file mode 100644 index 0000000000..ae2eb0b0c2 --- /dev/null +++ b/smart-contracts/contracts-as/test/overwrite-uref-content/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/overwrite_uref_content.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/index.ts b/smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/index.ts new file mode 100644 index 0000000000..51dfe28dfb --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/index.ts @@ -0,0 +1,57 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error} from "../../../../contract-as/assembly/error"; +import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; +import {Key} from "../../../../contract-as/assembly/key"; +import {putKey} from "../../../../contract-as/assembly"; +import {CLValue} from "../../../../contract-as/assembly/clvalue"; +import { RuntimeArgs } from "../../../../contract-as/assembly/runtime_args"; +import {Pair} from "../../../../contract-as/assembly/pair"; + +const METHOD_VERSION = "version"; +const HASH_KEY_NAME = "purse_holder"; +const ENTRY_POINT_NAME = "entry_point"; +const PURSE_NAME = "purse_name"; + +enum CustomError { + UnableToGetVersion = 6, + UnableToStoreVersion = 7, + InvalidVersion = 8 +} + +export function call(): void { + let entryPointNameBytes = CL.getNamedArg(ENTRY_POINT_NAME); + let entryPointName = fromBytesString(entryPointNameBytes).unwrap(); + + // short circuit if VERSION method called + if (entryPointName == METHOD_VERSION) { + let contractHash = CL.getNamedArg(HASH_KEY_NAME); + const versionBytes = CL.callContract(contractHash, entryPointName, new RuntimeArgs()); + if (versionBytes === null) { + Error.fromUserError(CustomError.UnableToGetVersion).revert(); + return; + } + const versionResult = fromBytesString(versionBytes); + if (versionResult.hasError()) { + Error.fromUserError(CustomError.InvalidVersion).revert(); + return; + } + let version = versionResult.value; + const maybeVersionKey = Key.create(CLValue.fromString(version)); + if (maybeVersionKey === null) { + Error.fromUserError(CustomError.UnableToStoreVersion).revert(); + return; + } + const versionKey = maybeVersionKey; + putKey(METHOD_VERSION, versionKey); + } + else { + let contractHash = CL.getNamedArg(HASH_KEY_NAME); + let purseNameBytes = CL.getNamedArg(PURSE_NAME); + let purseName = fromBytesString(purseNameBytes).unwrap(); + let runtimeArgs = RuntimeArgs.fromArray([ + new Pair(PURSE_NAME, CLValue.fromString(purseName)), + ]); + CL.callContract(contractHash, entryPointName, runtimeArgs); + } +} diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/tsconfig.json b/smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-caller/index.js b/smart-contracts/contracts-as/test/purse-holder-stored-caller/index.js new file mode 100644 index 0000000000..0b1094a1d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored-caller/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/purse_holder_stored_caller.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-caller/package.json b/smart-contracts/contracts-as/test/purse-holder-stored-caller/package.json new file mode 100644 index 0000000000..011b8f5aa5 --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored-caller/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/purse_holder_stored_caller.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/index.ts b/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/index.ts new file mode 100644 index 0000000000..2e1afc51b3 --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/index.ts @@ -0,0 +1,114 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; +import {Key} from "../../../../contract-as/assembly/key"; +import {CLValue, CLType, CLTypeTag} from "../../../../contract-as/assembly/clvalue"; +import {URef} from "../../../../contract-as/assembly/uref"; +import {createPurse} from "../../../../contract-as/assembly/purse"; +import { checkItemsEqual } from "../../../../contract-as/assembly/utils"; +import {Pair} from "../../../../contract-as/assembly/pair"; + +const METHOD_ADD = "add"; +const METHOD_REMOVE = "remove"; +const METHOD_VERSION = "version"; +const ARG_PURSE_NAME = "purse_name"; +const NEW_VERSION = "1.0.1"; +const VERSION = "version"; +const ACCESS_KEY_NAME = "purse_holder_access"; +const PURSE_HOLDER_STORED_CONTRACT_NAME = "purse_holder_stored"; +const ARG_CONTRACT_PACKAGE = "contract_package"; + +enum CustomError { + MissingPurseHolderURefArg = 0, + InvalidPurseHolderURefArg = 1, + MissingMethodNameArg = 2, + InvalidMethodNameArg = 3, + MissingPurseNameArg = 4, + InvalidPurseNameArg = 5, + UnknownMethodName = 6, + UnableToStoreVersion = 7, + NamedPurseNotCreated = 8 +} + + +function getPurseName(): String { + let purseNameBytes = CL.getNamedArg(ARG_PURSE_NAME); + return fromBytesString(purseNameBytes).unwrap(); +} + +export function add(): void { + let purseName = getPurseName(); + let purse = createPurse(); + CL.putKey(purseName, Key.fromURef(purse)); +} + +export function remove(): void { + let purseName = getPurseName(); + CL.removeKey(purseName); +} + +export function version(): void { + CL.ret(CLValue.fromString(VERSION)); +} + +export function delegate(): void { +} + +export function call(): void { + let contractPackageHash = CL.getNamedArg(ARG_CONTRACT_PACKAGE); + let accessKey = CL.getKey(ACCESS_KEY_NAME); + if (accessKey === null) { + Error.fromErrorCode(ErrorCode.GetKey).revert(); + return; + } + + let entryPoints = new CL.EntryPoints(); + + let addArgs = new Array>(); + addArgs.push(new Pair(ARG_PURSE_NAME, new CLType(CLTypeTag.String))); + + let add = new CL.EntryPoint( + METHOD_ADD, + addArgs, + new CLType(CLTypeTag.Unit), + new CL.PublicAccess(), + CL.EntryPointType.Contract, + ); + entryPoints.addEntryPoint(add); + + let version = new CL.EntryPoint( + METHOD_VERSION, + new Array>(), + new CLType(CLTypeTag.String), + new CL.PublicAccess(), + CL.EntryPointType.Contract, + ); + entryPoints.addEntryPoint(version); + + let removeArgs = new Array>(); + removeArgs.push(new Pair(ARG_PURSE_NAME, new CLType(CLTypeTag.String))); + + let remove = new CL.EntryPoint( + METHOD_REMOVE, + removeArgs, + new CLType(CLTypeTag.Unit), + new CL.PublicAccess(), + CL.EntryPointType.Contract, + ); + entryPoints.addEntryPoint(remove); + + let newResult = CL.addContractVersion( + contractPackageHash, + entryPoints, + new Array>(), + ); + CL.putKey(PURSE_HOLDER_STORED_CONTRACT_NAME, Key.fromHash(newResult.contractHash)); + + let newVersionKey = Key.create(CLValue.fromString(NEW_VERSION)); + if (newVersionKey === null) { + Error.fromErrorCode(ErrorCode.Formatting).revert(); + return; + } + CL.putKey(VERSION, newVersionKey); +} diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/tsconfig.json b/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/index.js b/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/index.js new file mode 100644 index 0000000000..53d717197f --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/purse_holder_stored_upgrader.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/package.json b/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/package.json new file mode 100644 index 0000000000..483a454df1 --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/purse_holder_stored_upgrader.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/purse-holder-stored/assembly/index.ts b/smart-contracts/contracts-as/test/purse-holder-stored/assembly/index.ts new file mode 100644 index 0000000000..a1553ffe91 --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored/assembly/index.ts @@ -0,0 +1,71 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {fromBytesString, toBytesMap} from "../../../../contract-as/assembly/bytesrepr"; +import {Key} from "../../../../contract-as/assembly/key"; +import {Pair} from "../../../../contract-as/assembly/pair"; +import {putKey, ret} from "../../../../contract-as/assembly"; +import {CLValue, CLType, CLTypeTag} from "../../../../contract-as/assembly/clvalue"; +import {createPurse} from "../../../../contract-as/assembly/purse"; +import {URef} from "../../../../contract-as/assembly/uref"; +import {CLTypeTag} from "../../../../contract-as/assembly/clvalue"; + +const METHOD_ADD = "add"; +const METHOD_REMOVE = "remove"; +const METHOD_VERSION = "version"; + +const ENTRY_POINT_ADD = "add_named_purse"; +const ENTRY_POINT_VERSION = "version"; +const HASH_KEY_NAME = "purse_holder"; +const ACCESS_KEY_NAME = "purse_holder_access"; +const ARG_PURSE = "purse_name"; +const VERSION = "1.0.0"; +const PURSE_HOLDER_STORED_CONTRACT_NAME = "purse_holder_stored"; + +enum CustomError { + MissingMethodNameArg = 0, + InvalidMethodNameArg = 1, + MissingPurseNameArg = 2, + InvalidPurseNameArg = 3, + UnknownMethodName = 4, + NamedPurseNotCreated = 5 +} + +export function add_named_purse(): void { + const purseNameBytes = CL.getNamedArg(ARG_PURSE); + const purseName = fromBytesString(purseNameBytes).unwrap(); + const purse = createPurse(); + CL.putKey(purseName, Key.fromURef(purse)); +} + +export function version(): void { + CL.ret(CLValue.fromString(VERSION)); +} + +export function call(): void { + let entryPoints = new CL.EntryPoints(); + + { + let args = new Array>(); + args.push(new Pair(ARG_PURSE, new CLType(CLTypeTag.String))); + let entryPointAdd = new CL.EntryPoint(ENTRY_POINT_ADD, args, new CLType(CLTypeTag.Unit), new CL.PublicAccess(), CL.EntryPointType.Contract); + entryPoints.addEntryPoint(entryPointAdd); + } + { + let entryPointAdd = new CL.EntryPoint(ENTRY_POINT_VERSION, new Array>(), new CLType(CLTypeTag.Unit), new CL.PublicAccess(), CL.EntryPointType.Contract); + entryPoints.addEntryPoint(entryPointAdd); + } + + let result = CL.newContract( + entryPoints, + null, + HASH_KEY_NAME, + ACCESS_KEY_NAME); + + putKey(PURSE_HOLDER_STORED_CONTRACT_NAME, Key.fromHash(result.contractHash)); + const versionKey = Key.create(CLValue.fromString(VERSION)); + if (versionKey === null) { + Error.fromErrorCode(ErrorCode.Formatting).revert(); + } + putKey(ENTRY_POINT_VERSION, versionKey); +} diff --git a/smart-contracts/contracts-as/test/purse-holder-stored/assembly/tsconfig.json b/smart-contracts/contracts-as/test/purse-holder-stored/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/purse-holder-stored/index.js b/smart-contracts/contracts-as/test/purse-holder-stored/index.js new file mode 100644 index 0000000000..6a8b60dcc6 --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/purse_holder_stored.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/purse-holder-stored/package.json b/smart-contracts/contracts-as/test/purse-holder-stored/package.json new file mode 100644 index 0000000000..2eeac8f7de --- /dev/null +++ b/smart-contracts/contracts-as/test/purse-holder-stored/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/purse_holder_stored.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/remove-associated-key/assembly/index.ts b/smart-contracts/contracts-as/test/remove-associated-key/assembly/index.ts new file mode 100644 index 0000000000..c1d04465f4 --- /dev/null +++ b/smart-contracts/contracts-as/test/remove-associated-key/assembly/index.ts @@ -0,0 +1,23 @@ +// The entry file of your WebAssembly module. +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {removeAssociatedKey, RemoveKeyFailure} from "../../../../contract-as/assembly/account"; +import {typedToArray} from "../../../../contract-as/assembly/utils"; +import {AccountHash} from "../../../../contract-as/assembly/key"; + +const ARG_ACCOUNT = "account"; + +export function call(): void { + let accountBytes = CL.getNamedArg(ARG_ACCOUNT); + const accountResult = AccountHash.fromBytes(accountBytes); + if (accountResult.hasError()) { + Error.fromErrorCode(ErrorCode.InvalidArgument).revert(); + return; + } + const account = accountResult.value; + + if (removeAssociatedKey(account) != RemoveKeyFailure.Ok) { + Error.fromUserError(4464).revert(); + return; + } +} diff --git a/smart-contracts/contracts-as/test/remove-associated-key/assembly/tsconfig.json b/smart-contracts/contracts-as/test/remove-associated-key/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/remove-associated-key/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/remove-associated-key/index.js b/smart-contracts/contracts-as/test/remove-associated-key/index.js new file mode 100644 index 0000000000..5a6adb78f3 --- /dev/null +++ b/smart-contracts/contracts-as/test/remove-associated-key/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/do_nothing.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/remove-associated-key/package.json b/smart-contracts/contracts-as/test/remove-associated-key/package.json new file mode 100644 index 0000000000..c130320ec9 --- /dev/null +++ b/smart-contracts/contracts-as/test/remove-associated-key/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/remove_associated_key.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/index.ts b/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/index.ts new file mode 100644 index 0000000000..ef8cb4bea5 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/index.ts @@ -0,0 +1,45 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error} from "../../../../contract-as/assembly/error"; +import {U512} from "../../../../contract-as/assembly/bignum"; +import {Key} from "../../../../contract-as/assembly/key"; +import {URef} from "../../../../contract-as/assembly/uref"; +import {putKey} from "../../../../contract-as/assembly"; +import {getMainPurse} from "../../../../contract-as/assembly/account"; +import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; +import {createPurse, transferFromPurseToPurse} from "../../../../contract-as/assembly/purse"; + + +const ARG_AMOUNT = "amount"; +const ARG_DESTINATION = "destination"; + +enum CustomError{ + InvalidAmountArg = 2, + InvalidDestinationArg = 4 +} + +export function call(): void { + const amountArg = CL.getNamedArg(ARG_AMOUNT); + const amountResult = U512.fromBytes(amountArg); + if (amountResult.hasError()) { + Error.fromUserError(CustomError.InvalidAmountArg).revert(); + return; + } + let amount = amountResult.value; + const destinationPurseNameArg = CL.getNamedArg(ARG_DESTINATION); + const destinationPurseNameResult = fromBytesString(destinationPurseNameArg); + if (destinationPurseNameResult.hasError()) { + Error.fromUserError(CustomError.InvalidDestinationArg).revert(); + return; + } + let destinationPurseName = destinationPurseNameResult.value; + const mainPurse = getMainPurse(); + const destinationPurse = createPurse(); + const result = transferFromPurseToPurse(mainPurse, destinationPurse, amount); + const error = Error.fromResult(result); + if (error !== null) { + error.revert(); + return; + } + putKey(destinationPurseName, Key.fromURef(destinationPurse)); +} diff --git a/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/tsconfig.json b/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/index.js b/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/index.js new file mode 100644 index 0000000000..a458c1d3a0 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/transfer_main_purse_to_new_purse.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/package.json b/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/package.json new file mode 100644 index 0000000000..6c652b2f99 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/transfer_main_purse_to_new_purse.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/index.ts b/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/index.ts new file mode 100644 index 0000000000..8a3b4adfe1 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/index.ts @@ -0,0 +1,49 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {U512} from "../../../../contract-as/assembly/bignum"; +import {getMainPurse} from "../../../../contract-as/assembly/account"; +import {Key} from "../../../../contract-as/assembly/key"; +import {putKey} from "../../../../contract-as/assembly"; +import {CLValue, CLType, CLTypeTag} from "../../../../contract-as/assembly/clvalue"; +import {URef} from "../../../../contract-as/assembly/uref"; +import {toBytesMap} from "../../../../contract-as/assembly/bytesrepr"; +import * as TransferPurseToAccount from "../../transfer-purse-to-account/assembly"; +import {getPurseBalance, transferFromPurseToAccount, TransferredTo} from "../../../../contract-as/assembly/purse"; +import {Pair} from "../../../../contract-as/assembly/pair"; + +const ENTRY_FUNCTION_NAME = "transfer"; +const PACKAGE_HASH_KEY_NAME = "transfer_purse_to_account"; +const HASH_KEY_NAME = "transfer_purse_to_account_hash"; +const ACCESS_KEY_NAME = "transfer_purse_to_account_access"; +const ARG_0_NAME = "target_account_addr"; +const ARG_1_NAME = "amount"; + + +enum CustomError{ + MissingAmountArg = 1, + InvalidAmountArg = 2, + MissingDestinationAccountArg = 3, + UnableToGetBalance = 103 +} + +export function transfer(): void { + TransferPurseToAccount.delegate(); +} + +export function call(): void { + let entryPoints = new CL.EntryPoints(); + let args = new Array>(); + args.push(new Pair(ARG_0_NAME, CLType.fixedList(new CLType(CLTypeTag.U8), 32))); + args.push(new Pair(ARG_1_NAME, new CLType(CLTypeTag.U512))); + + let entryPoint = new CL.EntryPoint(ENTRY_FUNCTION_NAME, args, new CLType(CLTypeTag.Unit), new CL.PublicAccess(), CL.EntryPointType.Session); + entryPoints.addEntryPoint(entryPoint); + let newResult = CL.newContract( + entryPoints, + null, + PACKAGE_HASH_KEY_NAME, + ACCESS_KEY_NAME, + ); + CL.putKey(HASH_KEY_NAME, Key.fromHash(newResult.contractHash)); +} \ No newline at end of file diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/tsconfig.json b/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/index.js b/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/index.js new file mode 100644 index 0000000000..6d824f2f58 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/transfer_purse_to_account_stored.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/package.json b/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/package.json new file mode 100644 index 0000000000..93a0ef1118 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/transfer_purse_to_account_stored.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/index.ts b/smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/index.ts new file mode 100644 index 0000000000..61d02e8889 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/index.ts @@ -0,0 +1,62 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import {U512} from "../../../../contract-as/assembly/bignum"; +import {getMainPurse} from "../../../../contract-as/assembly/account"; +import {Key} from "../../../../contract-as/assembly/key"; +import {putKey} from "../../../../contract-as/assembly"; +import {CLValue} from "../../../../contract-as/assembly/clvalue"; +import {getPurseBalance, transferFromPurseToAccount, TransferredTo} from "../../../../contract-as/assembly/purse"; +import {URef} from "../../../../contract-as/assembly/uref"; + + +const TRANSFER_RESULT_UREF_NAME = "transfer_result"; +const MAIN_PURSE_FINAL_BALANCE_UREF_NAME = "final_balance"; + +const ARG_TARGET = "target"; +const ARG_AMOUNT = "amount"; + +enum CustomError{ + MissingAmountArg = 1, + InvalidAmountArg = 2, + MissingDestinationAccountArg = 3, + UnableToGetBalance = 103 +} + +export function delegate(): void { + const mainPurse = getMainPurse(); + const destinationAccountAddrArg = CL.getNamedArg(ARG_TARGET); + const amountArg = CL.getNamedArg(ARG_AMOUNT); + const amountResult = U512.fromBytes(amountArg); + if (amountResult.hasError()) { + Error.fromUserError(CustomError.InvalidAmountArg).revert(); + return; + } + let amount = amountResult.value; + let message = ""; + const result = transferFromPurseToAccount(mainPurse, destinationAccountAddrArg, amount); + switch (result) { + case TransferredTo.NewAccount: + message = "Ok(NewAccount)"; + break; + case TransferredTo.ExistingAccount: + message = "Ok(ExistingAccount)"; + break; + case TransferredTo.TransferError: + message = "Err(ApiError::Transfer [" + ErrorCode.Transfer.toString() + "])"; + break; + } + const transferResultKey = Key.create(CLValue.fromString(message)); + putKey(TRANSFER_RESULT_UREF_NAME, transferResultKey); + const maybeBalance = getPurseBalance(mainPurse); + if (maybeBalance === null) { + Error.fromUserError(CustomError.UnableToGetBalance).revert(); + return; + } + const key = Key.create(CLValue.fromU512(maybeBalance)); + putKey(MAIN_PURSE_FINAL_BALANCE_UREF_NAME, key); +} + +export function call(): void { + delegate(); +} diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/tsconfig.json b/smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account/index.js b/smart-contracts/contracts-as/test/transfer-purse-to-account/index.js new file mode 100644 index 0000000000..a57b2d2ac1 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-account/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/transfer_purse_to_account.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account/package.json b/smart-contracts/contracts-as/test/transfer-purse-to-account/package.json new file mode 100644 index 0000000000..12cb453243 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-account/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/transfer_purse_to_account.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/index.ts b/smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/index.ts new file mode 100644 index 0000000000..efbac9d0fd --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/index.ts @@ -0,0 +1,126 @@ +//@ts-nocheck +import * as CL from "../../../../contract-as/assembly"; +import {Error} from "../../../../contract-as/assembly/error"; +import {U512} from "../../../../contract-as/assembly/bignum"; +import {getMainPurse} from "../../../../contract-as/assembly/account"; +import {Key} from "../../../../contract-as/assembly/key"; +import {getKey, hasKey, putKey} from "../../../../contract-as/assembly"; +import {CLValue} from "../../../../contract-as/assembly/clvalue"; +import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; +import {URef} from "../../../../contract-as/assembly/uref"; +import {createPurse, getPurseBalance, transferFromPurseToPurse} from "../../../../contract-as/assembly/purse"; + +const PURSE_MAIN = "purse:main"; +const PURSE_TRANSFER_RESULT = "purse_transfer_result"; +const MAIN_PURSE_BALANCE = "main_purse_balance"; +const SUCCESS_MESSAGE = "Ok(())"; +const TRANSFER_ERROR_MESSAGE = "Err(ApiError::Transfer [14])"; + +const ARG_SOURCE = "source"; +const ARG_TARGET = "target"; +const ARG_AMOUNT = "amount"; + +enum CustomError { + MissingSourcePurseArg = 1, + InvalidSourcePurseArg = 2, + MissingDestinationPurseArg = 3, + InvalidDestinationPurseArg = 4, + MissingDestinationPurse = 5, + UnableToStoreResult = 6, + UnableToStoreBalance = 7, + MissingAmountArg = 8, + InvalidAmountArg = 9, + InvalidSourcePurseKey = 103, + UnexpectedSourcePurseKeyVariant = 104, + InvalidDestinationPurseKey = 105, + UnexpectedDestinationPurseKeyVariant = 106, + UnableToGetBalance = 107, +} + +export function call(): void { + const mainPurse = getMainPurse(); + const mainPurseKey = Key.fromURef(mainPurse); + putKey(PURSE_MAIN, mainPurseKey); + const sourcePurseKeyNameArg = CL.getNamedArg(ARG_SOURCE); + const maybeSourcePurseKeyName = fromBytesString(sourcePurseKeyNameArg); + if(maybeSourcePurseKeyName.hasError()) { + Error.fromUserError(CustomError.InvalidSourcePurseArg).revert(); + return; + } + const sourcePurseKeyName = maybeSourcePurseKeyName.value; + const sourcePurseKey = getKey(sourcePurseKeyName); + if (sourcePurseKey === null){ + Error.fromUserError(CustomError.InvalidSourcePurseKey).revert(); + return; + } + if(!sourcePurseKey.isURef()){ + Error.fromUserError(CustomError.UnexpectedSourcePurseKeyVariant).revert(); + return; + } + const sourcePurse = sourcePurseKey.toURef(); + + const destinationPurseKeyNameArg = CL.getNamedArg(ARG_TARGET); + if (destinationPurseKeyNameArg === null) { + Error.fromUserError(CustomError.MissingDestinationPurseArg).revert(); + return; + } + const maybeDestinationPurseKeyName = fromBytesString(destinationPurseKeyNameArg); + if(maybeDestinationPurseKeyName.hasError()){ + Error.fromUserError(CustomError.InvalidDestinationPurseArg).revert(); + return; + } + let destinationPurseKeyName = maybeDestinationPurseKeyName.value; + let destinationPurse: URef | null; + let destinationKey: Key | null; + if(!hasKey(destinationPurseKeyName)){ + destinationPurse = createPurse(); + destinationKey = Key.fromURef(destinationPurse); + putKey(destinationPurseKeyName, destinationKey); + } else { + destinationKey = getKey(destinationPurseKeyName); + if(destinationKey === null){ + Error.fromUserError(CustomError.InvalidDestinationPurseKey).revert(); + return; + } + if(!destinationKey.isURef()){ + Error.fromUserError(CustomError.UnexpectedDestinationPurseKeyVariant).revert(); + return; + } + destinationPurse = destinationKey.toURef(); + } + if(destinationPurse === null){ + Error.fromUserError(CustomError.MissingDestinationPurse).revert(); + return; + } + + const amountArg = CL.getNamedArg(ARG_AMOUNT); + const amountResult = U512.fromBytes(amountArg); + if (amountResult.hasError()) { + Error.fromUserError(CustomError.InvalidAmountArg).revert(); + return; + } + const amount = amountResult.value; + + const result = transferFromPurseToPurse(sourcePurse, destinationPurse, amount); + let message = SUCCESS_MESSAGE; + if (result > 0){ + message = TRANSFER_ERROR_MESSAGE; + } + const resultKey = Key.create(CLValue.fromString(message)); + const finalBalance = getPurseBalance(sourcePurse); + if(finalBalance === null){ + Error.fromUserError(CustomError.UnableToGetBalance).revert(); + return; + } + const balanceKey = Key.create(CLValue.fromU512(finalBalance)); + if(balanceKey === null){ + Error.fromUserError(CustomError.UnableToStoreBalance).revert(); + return; + } + if(resultKey === null){ + Error.fromUserError(CustomError.UnableToStoreResult).revert(); + return; + } + putKey(PURSE_TRANSFER_RESULT, resultKey); + putKey(MAIN_PURSE_BALANCE, balanceKey); +} diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/tsconfig.json b/smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/tsconfig.json new file mode 100644 index 0000000000..505b0fc0d8 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../../../../../../.nvm/versions/node/v10.16.3/lib/node_modules/assemblyscript/std/assembly.json", + "include": [ + "./**/*.ts" + ] +} diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-purse/index.js b/smart-contracts/contracts-as/test/transfer-purse-to-purse/index.js new file mode 100644 index 0000000000..82bdb3360b --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-purse/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/transfer_purse_to_purse.wasm")); +const imports = { + env: { + abort(_msg, _file, line, column) { + console.error("abort called at index.ts:" + line + ":" + column); + } + } +}; +Object.defineProperty(module, "exports", { + get: () => new WebAssembly.Instance(compiled, imports).exports +}); diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-purse/package.json b/smart-contracts/contracts-as/test/transfer-purse-to-purse/package.json new file mode 100644 index 0000000000..013fd83358 --- /dev/null +++ b/smart-contracts/contracts-as/test/transfer-purse-to-purse/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/transfer_purse_to_purse.wasm --optimize --use abort=", + "asbuild": "npm run asbuild:optimized" + }, + "devDependencies": { + "assemblyscript": "^0.8.1" + } +} diff --git a/smart-contracts/contracts/.cargo/config b/smart-contracts/contracts/.cargo/config new file mode 100644 index 0000000000..f4e8c002fc --- /dev/null +++ b/smart-contracts/contracts/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/smart-contracts/contracts/SRE/create-test-node-01/Cargo.toml b/smart-contracts/contracts/SRE/create-test-node-01/Cargo.toml new file mode 100644 index 0000000000..62fe19e68c --- /dev/null +++ b/smart-contracts/contracts/SRE/create-test-node-01/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "create-test-node-01" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[[bin]] +name = "create_test_node_01" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +create-test-node-shared = { path = "../create-test-node-shared" } diff --git a/smart-contracts/contracts/SRE/create-test-node-01/src/main.rs b/smart-contracts/contracts/SRE/create-test-node-01/src/main.rs new file mode 100644 index 0000000000..b9a35b6ef4 --- /dev/null +++ b/smart-contracts/contracts/SRE/create-test-node-01/src/main.rs @@ -0,0 +1,10 @@ +#![no_std] +#![no_main] + +const NODE_01_ADDR: &[u8; 64] = b"d853ee569a6cf4315a26cf1190f9b55003aae433bd732453b967742b883da0b2"; +const INITIAL_AMOUNT: u64 = 1_000_000; + +#[no_mangle] +pub extern "C" fn call() { + create_test_node_shared::create_account(NODE_01_ADDR, INITIAL_AMOUNT) +} diff --git a/smart-contracts/contracts/SRE/create-test-node-02/Cargo.toml b/smart-contracts/contracts/SRE/create-test-node-02/Cargo.toml new file mode 100644 index 0000000000..81137a6de9 --- /dev/null +++ b/smart-contracts/contracts/SRE/create-test-node-02/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "create-test-node-02" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[[bin]] +name = "create_test_node_02" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +create-test-node-shared = { path = "../create-test-node-shared" } diff --git a/smart-contracts/contracts/SRE/create-test-node-02/src/main.rs b/smart-contracts/contracts/SRE/create-test-node-02/src/main.rs new file mode 100644 index 0000000000..0907a9421d --- /dev/null +++ b/smart-contracts/contracts/SRE/create-test-node-02/src/main.rs @@ -0,0 +1,10 @@ +#![no_std] +#![no_main] + +const NODE_02_ADDR: &[u8; 64] = b"4ee7ad9b21fd625481d0a94c618a15ab92503a7457e428a4dcd9dd6f100e979b"; +const INITIAL_AMOUNT: u64 = 1_000_000; + +#[no_mangle] +pub extern "C" fn call() { + create_test_node_shared::create_account(NODE_02_ADDR, INITIAL_AMOUNT) +} diff --git a/smart-contracts/contracts/SRE/create-test-node-03/Cargo.toml b/smart-contracts/contracts/SRE/create-test-node-03/Cargo.toml new file mode 100644 index 0000000000..324d589248 --- /dev/null +++ b/smart-contracts/contracts/SRE/create-test-node-03/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "create-test-node-03" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[[bin]] +name = "create_test_node_03" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +create-test-node-shared = { path = "../create-test-node-shared" } diff --git a/smart-contracts/contracts/SRE/create-test-node-03/src/main.rs b/smart-contracts/contracts/SRE/create-test-node-03/src/main.rs new file mode 100644 index 0000000000..525d940897 --- /dev/null +++ b/smart-contracts/contracts/SRE/create-test-node-03/src/main.rs @@ -0,0 +1,10 @@ +#![no_std] +#![no_main] + +const NODE_03_ADDR: &[u8; 64] = b"a3b2fd2971f2de5145d2342df38555ce97070a27ef7e74b63e08c482697308dd"; +const INITIAL_AMOUNT: u64 = 1_000_000; + +#[no_mangle] +pub extern "C" fn call() { + create_test_node_shared::create_account(NODE_03_ADDR, INITIAL_AMOUNT) +} diff --git a/smart-contracts/contracts/SRE/create-test-node-shared/Cargo.toml b/smart-contracts/contracts/SRE/create-test-node-shared/Cargo.toml new file mode 100644 index 0000000000..5358aef305 --- /dev/null +++ b/smart-contracts/contracts/SRE/create-test-node-shared/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "create-test-node-shared" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[features] +std = ["base16/std", "contract/std"] + +[dependencies] +base16 = { version = "0.2.1", default-features = false } +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/SRE/create-test-node-shared/src/lib.rs b/smart-contracts/contracts/SRE/create-test-node-shared/src/lib.rs new file mode 100644 index 0000000000..3e19c824c7 --- /dev/null +++ b/smart-contracts/contracts/SRE/create-test-node-shared/src/lib.rs @@ -0,0 +1,43 @@ +#![no_std] + +use contract::{ + contract_api::{runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{account::AccountHash, ApiError, TransferredTo, U512}; + +#[repr(u16)] +enum Error { + AccountAlreadyExists = 10, + TransferFailed = 11, + FailedToParseAccountHash = 12, +} + +impl Into for Error { + fn into(self) -> ApiError { + ApiError::User(self as u16) + } +} + +fn parse_account_hash(hex: &[u8]) -> AccountHash { + let mut buffer = [0u8; 32]; + let bytes_written = base16::decode_slice(hex, &mut buffer) + .ok() + .unwrap_or_revert_with(Error::FailedToParseAccountHash); + if bytes_written != buffer.len() { + runtime::revert(Error::FailedToParseAccountHash) + } + AccountHash::new(buffer) +} + +pub fn create_account(account_addr: &[u8; 64], initial_amount: u64) { + let account_hash = parse_account_hash(account_addr); + let amount: U512 = U512::from(initial_amount); + + match system::transfer_to_account(account_hash, amount) + .unwrap_or_revert_with(Error::TransferFailed) + { + TransferredTo::NewAccount => (), + TransferredTo::ExistingAccount => runtime::revert(Error::AccountAlreadyExists), + } +} diff --git a/smart-contracts/contracts/bench/create-accounts/Cargo.toml b/smart-contracts/contracts/bench/create-accounts/Cargo.toml new file mode 100644 index 0000000000..3d0066c69c --- /dev/null +++ b/smart-contracts/contracts/bench/create-accounts/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "create-accounts" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "create_accounts" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/bench/create-accounts/src/main.rs b/smart-contracts/contracts/bench/create-accounts/src/main.rs new file mode 100644 index 0000000000..6a2c86ec6c --- /dev/null +++ b/smart-contracts/contracts/bench/create-accounts/src/main.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::vec::Vec; + +use contract::{ + contract_api::{runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{account::AccountHash, ApiError, U512}; + +const ARG_ACCOUNTS: &str = "accounts"; +const ARG_SEED_AMOUNT: &str = "seed_amount"; + +#[no_mangle] +pub extern "C" fn call() { + let accounts: Vec = runtime::get_named_arg(ARG_ACCOUNTS); + let seed_amount: U512 = runtime::get_named_arg(ARG_SEED_AMOUNT); + for account_hash in accounts { + system::transfer_to_account(account_hash, seed_amount) + .unwrap_or_revert_with(ApiError::Transfer); + } +} diff --git a/smart-contracts/contracts/bench/create-purses/Cargo.toml b/smart-contracts/contracts/bench/create-purses/Cargo.toml new file mode 100644 index 0000000000..c1fe922f4c --- /dev/null +++ b/smart-contracts/contracts/bench/create-purses/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "create-purses" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "create_purses" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/bench/create-purses/src/main.rs b/smart-contracts/contracts/bench/create-purses/src/main.rs new file mode 100644 index 0000000000..6e34ec86e9 --- /dev/null +++ b/smart-contracts/contracts/bench/create-purses/src/main.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate alloc; + +extern crate contract; + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::U512; + +const ARG_TOTAL_PURSES: &str = "total_purses"; +const ARG_SEED_AMOUNT: &str = "seed_amount"; + +#[no_mangle] +pub extern "C" fn call() { + let total_purses: u64 = runtime::get_named_arg(ARG_TOTAL_PURSES); + let seed_amount: U512 = runtime::get_named_arg(ARG_SEED_AMOUNT); + + for i in 0..total_purses { + let new_purse = system::create_purse(); + system::transfer_from_purse_to_purse(account::get_main_purse(), new_purse, seed_amount) + .unwrap_or_revert(); + + let name = format!("purse:{}", i); + runtime::put_key(&name, new_purse.into()); + } +} diff --git a/smart-contracts/contracts/bench/transfer-to-existing-account/Cargo.toml b/smart-contracts/contracts/bench/transfer-to-existing-account/Cargo.toml new file mode 100644 index 0000000000..8b8249feae --- /dev/null +++ b/smart-contracts/contracts/bench/transfer-to-existing-account/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "transfer-to-existing-account" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "transfer_to_existing_account" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/bench/transfer-to-existing-account/src/main.rs b/smart-contracts/contracts/bench/transfer-to-existing-account/src/main.rs new file mode 100644 index 0000000000..6ad03d3ee6 --- /dev/null +++ b/smart-contracts/contracts/bench/transfer-to-existing-account/src/main.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{account::AccountHash, ApiError, TransferredTo, U512}; + +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +#[repr(u16)] +enum Error { + TransferredToNewAccount = 0, +} + +#[no_mangle] +pub extern "C" fn call() { + let account: AccountHash = runtime::get_named_arg(ARG_TARGET); + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + let result = system::transfer_to_account(account, amount).unwrap_or_revert(); + match result { + TransferredTo::ExistingAccount => { + // This is the expected result, as all accounts have to be initialized beforehand + } + TransferredTo::NewAccount => { + runtime::revert(ApiError::User(Error::TransferredToNewAccount as u16)) + } + } +} diff --git a/smart-contracts/contracts/bench/transfer-to-purse/Cargo.toml b/smart-contracts/contracts/bench/transfer-to-purse/Cargo.toml new file mode 100644 index 0000000000..72238e7d3d --- /dev/null +++ b/smart-contracts/contracts/bench/transfer-to-purse/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "transfer-to-purse" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "transfer_to_purse" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/bench/transfer-to-purse/src/main.rs b/smart-contracts/contracts/bench/transfer-to-purse/src/main.rs new file mode 100644 index 0000000000..cf0b5f586a --- /dev/null +++ b/smart-contracts/contracts/bench/transfer-to-purse/src/main.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{URef, U512}; + +const ARG_TARGET_PURSE: &str = "target_purse"; +const ARG_AMOUNT: &str = "amount"; + +#[no_mangle] +pub extern "C" fn call() { + let target_purse: URef = runtime::get_named_arg(ARG_TARGET_PURSE); + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + + let source_purse = account::get_main_purse(); + + system::transfer_from_purse_to_purse(source_purse, target_purse, amount).unwrap_or_revert(); +} diff --git a/smart-contracts/contracts/client/bonding/Cargo.toml b/smart-contracts/contracts/client/bonding/Cargo.toml new file mode 100644 index 0000000000..d8c7563b7a --- /dev/null +++ b/smart-contracts/contracts/client/bonding/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bonding" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "bonding" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/client/bonding/src/main.rs b/smart-contracts/contracts/client/bonding/src/main.rs new file mode 100644 index 0000000000..9fed67abca --- /dev/null +++ b/smart-contracts/contracts/client/bonding/src/main.rs @@ -0,0 +1,45 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{runtime_args, RuntimeArgs, U512}; + +const BOND_METHOD_NAME: &str = "bond"; + +const ARG_AMOUNT: &str = "amount"; +const ARG_PURSE: &str = "purse"; + +// Bonding contract. +// +// Accepts bonding amount (of type `u64`) as first argument. +// Issues bonding request to the PoS contract. +#[no_mangle] +pub extern "C" fn call() { + // get bond amount arg + let bond_amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + + // provision bonding purse + let bonding_purse = { + let bonding_purse = system::create_purse(); + let source_purse = account::get_main_purse(); + // transfer amount to be bonded to bonding purse + system::transfer_from_purse_to_purse(source_purse, bonding_purse, bond_amount) + .unwrap_or_revert(); + bonding_purse + }; + + // bond + { + let contract_hash = system::get_proof_of_stake(); + let args = runtime_args! { + ARG_AMOUNT => bond_amount, + ARG_PURSE => bonding_purse, + }; + runtime::call_contract(contract_hash, BOND_METHOD_NAME, args) + } +} diff --git a/smart-contracts/contracts/client/counter-define/Cargo.toml b/smart-contracts/contracts/client/counter-define/Cargo.toml new file mode 100644 index 0000000000..8e7e2a6afe --- /dev/null +++ b/smart-contracts/contracts/client/counter-define/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "counter-define" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "counter_define" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/client/counter-define/src/main.rs b/smart-contracts/contracts/client/counter-define/src/main.rs new file mode 100644 index 0000000000..8642c51105 --- /dev/null +++ b/smart-contracts/contracts/client/counter-define/src/main.rs @@ -0,0 +1,168 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{string::String, vec, vec::Vec}; +use core::convert::TryInto; + +use alloc::boxed::Box; +use contract::{ + contract_api::{self, runtime, storage}, + ext_ffi, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + api_error::{self}, + bytesrepr::{self}, + contracts::NamedKeys, + runtime_args, ApiError, CLType, CLValue, ContractPackageHash, EntryPoint, EntryPointAccess, + EntryPointType, EntryPoints, Key, Parameter, RuntimeArgs, URef, +}; + +const HASH_KEY_NAME: &str = "counter_package_hash"; +const ACCESS_KEY_NAME: &str = "counter_package_access"; +const CONTRACT_VERSION_KEY: &str = "contract_version"; +const ENTRYPOINT_SESSION: &str = "session"; +const ENTRYPOINT_COUNTER: &str = "counter"; +const ARG_COUNTER_METHOD: &str = "method"; +const ARG_CONTRACT_HASH_NAME: &str = "counter_contract_hash"; +const COUNTER_VALUE_UREF: &str = "counter"; +const METHOD_GET: &str = "get"; +const METHOD_INC: &str = "inc"; + +#[no_mangle] +pub extern "C" fn counter() { + let uref = runtime::get_key(COUNTER_VALUE_UREF) + .unwrap_or_revert() + .try_into() + .unwrap_or_revert(); + + let method_name: String = runtime::get_named_arg(ARG_COUNTER_METHOD); + + match method_name.as_str() { + METHOD_INC => storage::add(uref, 1), + METHOD_GET => { + let result: i32 = storage::read_or_revert(uref); + let return_value = CLValue::from_t(result).unwrap_or_revert(); + runtime::ret(return_value); + } + _ => runtime::revert(ApiError::InvalidArgument), + } +} + +#[no_mangle] +pub extern "C" fn session() { + let counter_key = get_counter_key(); + let contract_hash = counter_key + .into_hash() + .unwrap_or_revert_with(ApiError::UnexpectedKeyVariant); + let entry_point_name = ENTRYPOINT_COUNTER; + let runtime_args = runtime_args! { ARG_COUNTER_METHOD => METHOD_INC }; + runtime::call_contract(contract_hash, entry_point_name, runtime_args) +} + +#[no_mangle] +pub extern "C" fn call() { + let (contract_package_hash, access_uref): (ContractPackageHash, URef) = + storage::create_contract_package_at_hash(); + runtime::put_key(HASH_KEY_NAME, contract_package_hash.into()); + runtime::put_key(ACCESS_KEY_NAME, access_uref.into()); + + let entry_points = get_entry_points(); + let count_value_uref = storage::new_uref(0); //initialize counter + let named_keys = { + let mut ret = NamedKeys::new(); + ret.insert(String::from(COUNTER_VALUE_UREF), count_value_uref.into()); + ret + }; + + let (contract_hash, contract_version) = + storage::add_contract_version(contract_package_hash, entry_points, named_keys); + let version_uref = storage::new_uref(contract_version); + runtime::put_key(CONTRACT_VERSION_KEY, version_uref.into()); + runtime::put_key(ARG_CONTRACT_HASH_NAME, contract_hash.into()); +} + +fn get_entry_points() -> EntryPoints { + let mut entry_points = EntryPoints::new(); + + // actual stored contract + // ARG_METHOD -> METHOD_GET or METHOD_INC + // ret -> counter value + let entry_point = EntryPoint::new( + ENTRYPOINT_COUNTER, + vec![Parameter::new(ARG_COUNTER_METHOD, CLType::String)], + CLType::I32, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + + // stored session code that call a version of the stored contract + // ARG_CONTRACT_HASH -> ContractHash of METHOD_COUNTER + let entry_point = EntryPoint::new( + ENTRYPOINT_SESSION, + vec![Parameter::new( + ARG_CONTRACT_HASH_NAME, + CLType::FixedList(Box::new(CLType::U8), 32), + )], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(entry_point); + + entry_points +} + +fn get_counter_key() -> Key { + let name = ARG_CONTRACT_HASH_NAME; + let arg = { + let mut arg_size: usize = 0; + let ret = unsafe { + ext_ffi::get_named_arg_size( + name.as_bytes().as_ptr(), + name.len(), + &mut arg_size as *mut usize, + ) + }; + match api_error::result_from(ret) { + Ok(_) => { + if arg_size == 0 { + None + } else { + Some(arg_size) + } + } + Err(ApiError::MissingArgument) => None, + Err(e) => runtime::revert(e), + } + }; + + match arg { + Some(arg_size) => { + let arg_bytes = { + let res = { + let data_non_null_ptr = contract_api::alloc_bytes(arg_size); + let ret = unsafe { + ext_ffi::get_named_arg( + name.as_bytes().as_ptr(), + name.len(), + data_non_null_ptr.as_ptr(), + arg_size, + ) + }; + let data = unsafe { + Vec::from_raw_parts(data_non_null_ptr.as_ptr(), arg_size, arg_size) + }; + api_error::result_from(ret).map(|_| data) + }; + res.unwrap_or_revert() + }; + + bytesrepr::deserialize(arg_bytes).unwrap_or_revert_with(ApiError::InvalidArgument) + } + None => runtime::get_key(ARG_CONTRACT_HASH_NAME).unwrap_or_revert_with(ApiError::GetKey), + } +} diff --git a/smart-contracts/contracts/client/named-purse-payment/Cargo.toml b/smart-contracts/contracts/client/named-purse-payment/Cargo.toml new file mode 100644 index 0000000000..6cf4049003 --- /dev/null +++ b/smart-contracts/contracts/client/named-purse-payment/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "named-purse-payment" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "named_purse_payment" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/client/named-purse-payment/src/main.rs b/smart-contracts/contracts/client/named-purse-payment/src/main.rs new file mode 100644 index 0000000000..27df0403f2 --- /dev/null +++ b/smart-contracts/contracts/client/named-purse-payment/src/main.rs @@ -0,0 +1,57 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::{ + contract_api::{runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{runtime_args, ApiError, RuntimeArgs, URef, U512}; + +const GET_PAYMENT_PURSE: &str = "get_payment_purse"; +const SET_REFUND_PURSE: &str = "set_refund_purse"; + +const ARG_AMOUNT: &str = "amount"; +const ARG_PURSE: &str = "purse"; +const ARG_PURSE_NAME: &str = "purse_name"; + +/// This logic is intended to be used as SESSION PAYMENT LOGIC +/// Alternate payment logic that allows payment from a purse other than the executing [Account]'s +/// main purse. A `Key::Uref` to the source purse must already exist in the executing context's +/// named keys under the name passed in as the `purse_name` argument. +#[no_mangle] +pub extern "C" fn call() { + // source purse uref by name (from current context's named keys) + let purse_uref = { + let purse_name: String = runtime::get_named_arg(ARG_PURSE_NAME); + runtime::get_key(&purse_name) + .unwrap_or_revert_with(ApiError::InvalidPurseName) + .into_uref() + .unwrap_or_revert_with(ApiError::InvalidPurse) + }; + + // amount to transfer from named purse to payment purse + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + + // proof of stake contract + let pos_contract_hash = system::get_proof_of_stake(); + + // set refund purse to source purse + { + let contract_hash = pos_contract_hash; + let args = runtime_args! { + ARG_PURSE => purse_uref, + }; + runtime::call_contract::<()>(contract_hash, SET_REFUND_PURSE, args); + } + + // get payment purse for current execution + let payment_purse: URef = + runtime::call_contract(pos_contract_hash, GET_PAYMENT_PURSE, RuntimeArgs::default()); + + // transfer amount from named purse to payment purse, which will be used to pay for execution + system::transfer_from_purse_to_purse(purse_uref, payment_purse, amount).unwrap_or_revert(); +} diff --git a/smart-contracts/contracts/client/revert/Cargo.toml b/smart-contracts/contracts/client/revert/Cargo.toml new file mode 100644 index 0000000000..bf21d4fd26 --- /dev/null +++ b/smart-contracts/contracts/client/revert/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "revert" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[[bin]] +name = "revert" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/client/revert/src/main.rs b/smart-contracts/contracts/client/revert/src/main.rs new file mode 100644 index 0000000000..ba47add27b --- /dev/null +++ b/smart-contracts/contracts/client/revert/src/main.rs @@ -0,0 +1,10 @@ +#![no_std] +#![no_main] + +use contract::contract_api::runtime; +use types::ApiError; + +#[no_mangle] +pub extern "C" fn call() { + runtime::revert(ApiError::User(100)) +} diff --git a/smart-contracts/contracts/client/transfer-to-account-stored/Cargo.toml b/smart-contracts/contracts/client/transfer-to-account-stored/Cargo.toml new file mode 100644 index 0000000000..028c30fdc8 --- /dev/null +++ b/smart-contracts/contracts/client/transfer-to-account-stored/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "transfer-to-account-stored" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "transfer_to_account_stored" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +transfer-to-account = { path = "../transfer-to-account" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/client/transfer-to-account-stored/src/main.rs b/smart-contracts/contracts/client/transfer-to-account-stored/src/main.rs new file mode 100644 index 0000000000..77a9a80028 --- /dev/null +++ b/smart-contracts/contracts/client/transfer-to-account-stored/src/main.rs @@ -0,0 +1,53 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate alloc; + +use contract::contract_api::{runtime, storage}; +use types::{ + account::AccountHash, CLType, CLTyped, ContractHash, ContractVersion, EntryPoint, + EntryPointAccess, EntryPointType, EntryPoints, Parameter, +}; + +const CONTRACT_NAME: &str = "transfer_to_account"; +const CONTRACT_VERSION_KEY: &str = "contract_version"; +const FUNCTION_NAME: &str = "transfer"; + +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +#[no_mangle] +pub extern "C" fn transfer() { + transfer_to_account::delegate(); +} + +fn store() -> (ContractHash, ContractVersion) { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + FUNCTION_NAME, + vec![ + Parameter::new(ARG_TARGET, AccountHash::cl_type()), + Parameter::new(ARG_AMOUNT, CLType::U512), + ], + CLType::URef, + EntryPointAccess::Public, + EntryPointType::Session, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + storage::new_contract(entry_points, None, None, None) +} + +#[no_mangle] +pub extern "C" fn call() { + let (contract_hash, contract_version) = store(); + let version_uref = storage::new_uref(contract_version); + runtime::put_key(CONTRACT_VERSION_KEY, version_uref.into()); + runtime::put_key(CONTRACT_NAME, contract_hash.into()); +} diff --git a/smart-contracts/contracts/client/transfer-to-account-u512-stored/Cargo.toml b/smart-contracts/contracts/client/transfer-to-account-u512-stored/Cargo.toml new file mode 100644 index 0000000000..68b47f43a1 --- /dev/null +++ b/smart-contracts/contracts/client/transfer-to-account-u512-stored/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "transfer-to-account-u512-stored" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "transfer_to_account_u512_stored" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +transfer-to-account-u512 = { path = "../transfer-to-account-u512", package = "transfer-to-account-u512" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/client/transfer-to-account-u512-stored/src/main.rs b/smart-contracts/contracts/client/transfer-to-account-u512-stored/src/main.rs new file mode 100644 index 0000000000..b70b2db171 --- /dev/null +++ b/smart-contracts/contracts/client/transfer-to-account-u512-stored/src/main.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate alloc; + +use alloc::string::ToString; + +use contract::contract_api::{runtime, storage}; +use types::{ + account::AccountHash, CLType, CLTyped, ContractHash, ContractVersion, EntryPoint, + EntryPointAccess, EntryPointType, EntryPoints, Parameter, +}; + +const CONTRACT_NAME: &str = "transfer_to_account"; +const CONTRACT_VERSION_KEY: &str = "contract_version"; +const ENTRY_POINT_NAME: &str = "transfer"; +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +const HASH_KEY_NAME: &str = "transfer_to_account_U512"; +const ACCESS_KEY_NAME: &str = "transfer_to_account_U512_access"; + +#[no_mangle] +pub extern "C" fn transfer() { + transfer_to_account_u512::delegate(); +} + +fn store() -> (ContractHash, ContractVersion) { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + ENTRY_POINT_NAME, + vec![ + Parameter::new(ARG_TARGET, AccountHash::cl_type()), + Parameter::new(ARG_AMOUNT, CLType::U512), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + storage::new_contract( + entry_points, + None, + Some(HASH_KEY_NAME.to_string()), + Some(ACCESS_KEY_NAME.to_string()), + ) +} + +#[no_mangle] +pub extern "C" fn call() { + let (contract_hash, contract_version) = store(); + let version_uref = storage::new_uref(contract_version); + runtime::put_key(CONTRACT_VERSION_KEY, version_uref.into()); + runtime::put_key(CONTRACT_NAME, contract_hash.into()); +} diff --git a/smart-contracts/contracts/client/transfer-to-account-u512/Cargo.toml b/smart-contracts/contracts/client/transfer-to-account-u512/Cargo.toml new file mode 100644 index 0000000000..bcbabcf09c --- /dev/null +++ b/smart-contracts/contracts/client/transfer-to-account-u512/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "transfer-to-account-u512" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "transfer_to_account_u512" +path = "src/bin/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/client/transfer-to-account-u512/src/bin/main.rs b/smart-contracts/contracts/client/transfer-to-account-u512/src/bin/main.rs new file mode 100644 index 0000000000..ca01e85715 --- /dev/null +++ b/smart-contracts/contracts/client/transfer-to-account-u512/src/bin/main.rs @@ -0,0 +1,7 @@ +#![no_std] +#![no_main] + +#[no_mangle] +pub extern "C" fn call() { + transfer_to_account_u512::delegate(); +} diff --git a/smart-contracts/contracts/client/transfer-to-account-u512/src/lib.rs b/smart-contracts/contracts/client/transfer-to-account-u512/src/lib.rs new file mode 100644 index 0000000000..d0c2288d6f --- /dev/null +++ b/smart-contracts/contracts/client/transfer-to-account-u512/src/lib.rs @@ -0,0 +1,19 @@ +#![no_std] + +use contract::{ + contract_api::{runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{account::AccountHash, U512}; + +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +/// Executes mote transfer to supplied account hash. +/// Transfers the requested amount. +#[no_mangle] +pub fn delegate() { + let account_hash: AccountHash = runtime::get_named_arg(ARG_TARGET); + let transfer_amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + system::transfer_to_account(account_hash, transfer_amount).unwrap_or_revert(); +} diff --git a/smart-contracts/contracts/client/transfer-to-account/Cargo.toml b/smart-contracts/contracts/client/transfer-to-account/Cargo.toml new file mode 100644 index 0000000000..3461d38f4e --- /dev/null +++ b/smart-contracts/contracts/client/transfer-to-account/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "transfer-to-account" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "transfer_to_account" +path = "src/bin/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/client/transfer-to-account/src/bin/main.rs b/smart-contracts/contracts/client/transfer-to-account/src/bin/main.rs new file mode 100644 index 0000000000..93d4b79196 --- /dev/null +++ b/smart-contracts/contracts/client/transfer-to-account/src/bin/main.rs @@ -0,0 +1,7 @@ +#![no_std] +#![no_main] + +#[no_mangle] +pub extern "C" fn call() { + transfer_to_account::delegate(); +} diff --git a/smart-contracts/contracts/client/transfer-to-account/src/lib.rs b/smart-contracts/contracts/client/transfer-to-account/src/lib.rs new file mode 100644 index 0000000000..57c5256d54 --- /dev/null +++ b/smart-contracts/contracts/client/transfer-to-account/src/lib.rs @@ -0,0 +1,19 @@ +#![no_std] + +use contract::{ + contract_api::{runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{account::AccountHash, U512}; + +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +/// Executes mote transfer to supplied account hash. +/// Transfers the requested amount. +pub fn delegate() { + let account_hash: AccountHash = runtime::get_named_arg(ARG_TARGET); + let transfer_amount: u64 = runtime::get_named_arg(ARG_AMOUNT); + let u512_motes = U512::from(transfer_amount); + system::transfer_to_account(account_hash, u512_motes).unwrap_or_revert(); +} diff --git a/smart-contracts/contracts/client/unbonding/Cargo.toml b/smart-contracts/contracts/client/unbonding/Cargo.toml new file mode 100644 index 0000000000..a48901f136 --- /dev/null +++ b/smart-contracts/contracts/client/unbonding/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "unbonding" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "unbonding" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/client/unbonding/src/main.rs b/smart-contracts/contracts/client/unbonding/src/main.rs new file mode 100644 index 0000000000..3ba35739e0 --- /dev/null +++ b/smart-contracts/contracts/client/unbonding/src/main.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use contract::contract_api::{runtime, system}; +use types::{runtime_args, RuntimeArgs, U512}; + +const UNBOND_METHOD_NAME: &str = "unbond"; +const ARG_AMOUNT: &str = "amount"; + +// Unbonding contract. +// +// Accepts unbonding amount (of type `Option`) as first argument. +// Unbonding with `None` unbonds all stakes in the PoS contract. +// Otherwise (`Some`) unbonds with part of the bonded stakes. +#[no_mangle] +pub extern "C" fn call() { + let unbond_amount: Option = + runtime::get_named_arg::>(ARG_AMOUNT).map(Into::into); + + let contract_hash = system::get_proof_of_stake(); + let args = runtime_args! { + ARG_AMOUNT => unbond_amount, + }; + runtime::call_contract(contract_hash, UNBOND_METHOD_NAME, args) +} diff --git a/smart-contracts/contracts/explorer/faucet-stored/Cargo.toml b/smart-contracts/contracts/explorer/faucet-stored/Cargo.toml new file mode 100644 index 0000000000..73d040f65f --- /dev/null +++ b/smart-contracts/contracts/explorer/faucet-stored/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "faucet-stored" +version = "0.1.0" +authors = ["Mateusz Górski "] +edition = "2018" + +[[bin]] +name = "faucet_stored" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +faucet = { path = "../faucet", package = "faucet" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/explorer/faucet-stored/src/main.rs b/smart-contracts/contracts/explorer/faucet-stored/src/main.rs new file mode 100644 index 0000000000..460cdbf21e --- /dev/null +++ b/smart-contracts/contracts/explorer/faucet-stored/src/main.rs @@ -0,0 +1,59 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{string::ToString, vec}; + +use contract::contract_api::{runtime, storage}; +use types::{ + account::AccountHash, CLType, CLTyped, ContractHash, ContractVersion, EntryPoint, + EntryPointAccess, EntryPointType, EntryPoints, Parameter, +}; + +const CONTRACT_NAME: &str = "faucet"; +const HASH_KEY_NAME: &str = "faucet_package"; +const ACCESS_KEY_NAME: &str = "faucet_package_access"; +const ENTRY_POINT_NAME: &str = "call_faucet"; +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[no_mangle] +pub extern "C" fn call_faucet() { + faucet::delegate(); +} + +fn store() -> (ContractHash, ContractVersion) { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + ENTRY_POINT_NAME, + vec![ + Parameter::new(ARG_TARGET, AccountHash::cl_type()), + Parameter::new(ARG_AMOUNT, CLType::U512), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + storage::new_contract( + entry_points, + None, + Some(HASH_KEY_NAME.to_string()), + Some(ACCESS_KEY_NAME.to_string()), + ) +} + +#[no_mangle] +pub extern "C" fn call() { + let (contract_hash, contract_version) = store(); + runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into()); + runtime::put_key(CONTRACT_NAME, contract_hash.into()); +} diff --git a/smart-contracts/contracts/explorer/faucet/Cargo.toml b/smart-contracts/contracts/explorer/faucet/Cargo.toml new file mode 100644 index 0000000000..56bf2fde61 --- /dev/null +++ b/smart-contracts/contracts/explorer/faucet/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "faucet" +version = "0.1.0" +authors = ["Mateusz Górski "] +edition = "2018" + +[[bin]] +name = "faucet" +path = "src/bin/main.rs" +doctest = false +test = false +bench = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/explorer/faucet/src/bin/main.rs b/smart-contracts/contracts/explorer/faucet/src/bin/main.rs new file mode 100644 index 0000000000..6d29413335 --- /dev/null +++ b/smart-contracts/contracts/explorer/faucet/src/bin/main.rs @@ -0,0 +1,7 @@ +#![no_std] +#![no_main] + +#[no_mangle] +pub extern "C" fn call() { + faucet::delegate(); +} diff --git a/smart-contracts/contracts/explorer/faucet/src/lib.rs b/smart-contracts/contracts/explorer/faucet/src/lib.rs new file mode 100644 index 0000000000..993b7565d2 --- /dev/null +++ b/smart-contracts/contracts/explorer/faucet/src/lib.rs @@ -0,0 +1,38 @@ +#![no_std] + +use contract::{ + contract_api::{runtime, storage, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{account::AccountHash, ApiError, U512}; + +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +#[repr(u32)] +enum CustomError { + AlreadyFunded = 1, +} + +/// Executes token transfer to supplied account hash. +/// Revert status codes: +/// 1 - requested transfer to already funded account hash. +#[no_mangle] +pub fn delegate() { + let account_hash: AccountHash = runtime::get_named_arg(ARG_TARGET); + + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + + // Maybe we will decide to allow multiple funds up until some maximum value. + let already_funded = storage::read_local::(&account_hash) + .unwrap_or_default() + .is_some(); + + if already_funded { + runtime::revert(ApiError::User(CustomError::AlreadyFunded as u16)); + } else { + system::transfer_to_account(account_hash, amount).unwrap_or_revert(); + // Transfer successful; Store the fact of funding in the local state. + storage::write_local(account_hash, amount); + } +} diff --git a/smart-contracts/contracts/integration/add-associated-key/Cargo.toml b/smart-contracts/contracts/integration/add-associated-key/Cargo.toml new file mode 100644 index 0000000000..030fe52001 --- /dev/null +++ b/smart-contracts/contracts/integration/add-associated-key/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "add-associated-key" +version = "0.1.0" +authors = ["Joe Sacher "] +edition = "2018" + +[[bin]] +name = "add_associated_key" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/add-associated-key/src/main.rs b/smart-contracts/contracts/integration/add-associated-key/src/main.rs new file mode 100644 index 0000000000..3cf80b5dc3 --- /dev/null +++ b/smart-contracts/contracts/integration/add-associated-key/src/main.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{account, runtime}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + account::{AccountHash, Weight}, + ApiError, +}; + +#[repr(u16)] +enum Error { + AddAssociatedKey = 100, +} + +impl Into for Error { + fn into(self) -> ApiError { + ApiError::User(self as u16) + } +} + +const ARG_ACCOUNT: &str = "account"; +const ARG_WEIGHT: &str = "weight"; + +#[no_mangle] +pub extern "C" fn call() { + let account: AccountHash = runtime::get_named_arg(ARG_ACCOUNT); + let weight_val: u32 = runtime::get_named_arg(ARG_WEIGHT); + let weight = Weight::new(weight_val as u8); + + account::add_associated_key(account, weight).unwrap_or_revert_with(Error::AddAssociatedKey); +} diff --git a/smart-contracts/contracts/integration/args-multi/Cargo.toml b/smart-contracts/contracts/integration/args-multi/Cargo.toml new file mode 100644 index 0000000000..a5a9cf738b --- /dev/null +++ b/smart-contracts/contracts/integration/args-multi/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "args-multi" +version = "0.1.0" +authors = ["Joe Sacher "] +edition = "2018" + +[[bin]] +name = "args_multi" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/args-multi/src/main.rs b/smart-contracts/contracts/integration/args-multi/src/main.rs new file mode 100644 index 0000000000..1e2a35a54b --- /dev/null +++ b/smart-contracts/contracts/integration/args-multi/src/main.rs @@ -0,0 +1,19 @@ +#![no_std] +#![no_main] + +use contract::contract_api::runtime; +use types::ApiError; + +const ARG_ACCOUNT: &str = "account"; +const ARG_NUMBER: &str = "number"; + +#[no_mangle] +pub extern "C" fn call() { + let account_number: [u8; 32] = runtime::get_named_arg(ARG_ACCOUNT); + let number: u32 = runtime::get_named_arg(ARG_NUMBER); + + let account_sum: u8 = account_number.iter().sum(); + let total_sum: u32 = u32::from(account_sum) + number; + + runtime::revert(ApiError::User(total_sum as u16)); +} diff --git a/smart-contracts/contracts/integration/args-u32/Cargo.toml b/smart-contracts/contracts/integration/args-u32/Cargo.toml new file mode 100644 index 0000000000..ef6f1a7cb2 --- /dev/null +++ b/smart-contracts/contracts/integration/args-u32/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "args-u32" +version = "0.1.0" +authors = ["Piotr Kuchta "] +edition = "2018" + +[[bin]] +name = "args_u32" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/args-u32/src/main.rs b/smart-contracts/contracts/integration/args-u32/src/main.rs new file mode 100644 index 0000000000..dea8c6227d --- /dev/null +++ b/smart-contracts/contracts/integration/args-u32/src/main.rs @@ -0,0 +1,12 @@ +#![no_std] +#![no_main] + +use contract::contract_api::runtime; +use types::ApiError; +const ARG_NUMBER: &str = "number"; + +#[no_mangle] +pub extern "C" fn call() { + let number: u32 = runtime::get_named_arg(ARG_NUMBER); + runtime::revert(ApiError::User(number as u16)); +} diff --git a/smart-contracts/contracts/integration/args-u512/Cargo.toml b/smart-contracts/contracts/integration/args-u512/Cargo.toml new file mode 100644 index 0000000000..d5dffe5d63 --- /dev/null +++ b/smart-contracts/contracts/integration/args-u512/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "args-u512" +version = "0.1.0" +authors = ["Piotr Kuchta "] +edition = "2018" + +[[bin]] +name = "args_u512" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/args-u512/src/main.rs b/smart-contracts/contracts/integration/args-u512/src/main.rs new file mode 100644 index 0000000000..cbcc1d6778 --- /dev/null +++ b/smart-contracts/contracts/integration/args-u512/src/main.rs @@ -0,0 +1,15 @@ +#![no_std] +#![no_main] + +use contract::contract_api::runtime; +use types::{ApiError, U512}; + +const ARG_NUMBER: &str = "number"; + +#[no_mangle] +pub extern "C" fn call() { + let number: U512 = runtime::get_named_arg(ARG_NUMBER); + + let user_code: u16 = number.as_u32() as u16; + runtime::revert(ApiError::User(user_code)); +} diff --git a/smart-contracts/contracts/integration/create-named-purse/Cargo.toml b/smart-contracts/contracts/integration/create-named-purse/Cargo.toml new file mode 100644 index 0000000000..9250b6ed07 --- /dev/null +++ b/smart-contracts/contracts/integration/create-named-purse/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "create-named-purse" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "create_named_purse" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/create-named-purse/src/main.rs b/smart-contracts/contracts/integration/create-named-purse/src/main.rs new file mode 100644 index 0000000000..ebaa907c64 --- /dev/null +++ b/smart-contracts/contracts/integration/create-named-purse/src/main.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{Key, URef, U512}; + +const ARG_AMOUNT: &str = "amount"; +const ARG_NAME: &str = "name"; + +#[no_mangle] +pub extern "C" fn call() { + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + let name: String = runtime::get_named_arg(ARG_NAME); + let main_purse: URef = account::get_main_purse(); + let new_purse: URef = system::create_purse(); + + system::transfer_from_purse_to_purse(main_purse, new_purse, amount).unwrap_or_revert(); + + let new_purse_key: Key = new_purse.into(); + runtime::put_key(&name, new_purse_key); +} diff --git a/smart-contracts/contracts/integration/direct-revert/Cargo.toml b/smart-contracts/contracts/integration/direct-revert/Cargo.toml new file mode 100644 index 0000000000..a369a1f562 --- /dev/null +++ b/smart-contracts/contracts/integration/direct-revert/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "direct-revert" +version = "0.1.0" +authors = ["Piotr Kuchta "] +edition = "2018" + +[[bin]] +name = "direct_revert" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/direct-revert/src/main.rs b/smart-contracts/contracts/integration/direct-revert/src/main.rs new file mode 100644 index 0000000000..a9d7f701ec --- /dev/null +++ b/smart-contracts/contracts/integration/direct-revert/src/main.rs @@ -0,0 +1,11 @@ +#![no_std] +#![no_main] + +use contract::contract_api::runtime; +use types::ApiError; + +#[no_mangle] +pub extern "C" fn call() { + // Call revert with an application specific non-zero exit code. + runtime::revert(ApiError::User(1)); +} diff --git a/smart-contracts/contracts/integration/get-caller-call/Cargo.toml b/smart-contracts/contracts/integration/get-caller-call/Cargo.toml new file mode 100644 index 0000000000..46561ac8b8 --- /dev/null +++ b/smart-contracts/contracts/integration/get-caller-call/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "get-caller-call" +version = "0.1.0" +authors = ["Mateusz Górski "] +edition = "2018" + +[[bin]] +name = "get_caller_call" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/get-caller-call/src/main.rs b/smart-contracts/contracts/integration/get-caller-call/src/main.rs new file mode 100644 index 0000000000..177259b782 --- /dev/null +++ b/smart-contracts/contracts/integration/get-caller-call/src/main.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +use contract::{contract_api::runtime, unwrap_or_revert::UnwrapOrRevert}; +use types::{contracts::DEFAULT_ENTRY_POINT_NAME, ApiError, RuntimeArgs}; + +const GET_CALLER_KEY: &str = "get_caller"; + +#[no_mangle] +pub extern "C" fn call() { + let contract_hash = runtime::get_key(GET_CALLER_KEY) + .unwrap_or_revert_with(ApiError::GetKey) + .into_hash() + .unwrap_or_revert(); + // Call `define` part of the contract. + runtime::call_contract( + contract_hash, + DEFAULT_ENTRY_POINT_NAME, + RuntimeArgs::default(), + ) +} diff --git a/smart-contracts/contracts/integration/get-caller-define/Cargo.toml b/smart-contracts/contracts/integration/get-caller-define/Cargo.toml new file mode 100644 index 0000000000..d2253c580c --- /dev/null +++ b/smart-contracts/contracts/integration/get-caller-define/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "get-caller-define" +version = "0.1.0" +authors = ["Mateusz Górski "] +edition = "2018" + +[[bin]] +name = "get_caller_define" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/get-caller-define/src/main.rs b/smart-contracts/contracts/integration/get-caller-define/src/main.rs new file mode 100644 index 0000000000..5ec79cf0d5 --- /dev/null +++ b/smart-contracts/contracts/integration/get-caller-define/src/main.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use contract::contract_api::runtime; +use types::account::AccountHash; + +const _GET_CALLER_EXT: &str = "get_caller_ext"; +const _GET_CALLER_KEY: &str = "get_caller"; + +fn test_get_caller() { + // Assumes that will be called using test framework genesis account with + // account hash == 'ae7cd84d61ff556806691be61e6ab217791905677adbbe085b8c540d916e8393' + // Will fail if we ever change that. + let caller = runtime::get_caller(); + let expected_caller = AccountHash::new([ + 174, 124, 216, 77, 97, 255, 85, 104, 6, 105, 27, 230, 30, 106, 178, 23, 121, 25, 5, 103, + 122, 219, 190, 8, 91, 140, 84, 13, 145, 110, 131, 147, + ]); + assert_eq!(caller, expected_caller); +} + +#[no_mangle] +pub extern "C" fn get_caller_ext() { + // works in sub-calls + test_get_caller(); +} + +#[no_mangle] +pub extern "C" fn call() { + // works in session code + test_get_caller(); + + // TODO: new style version store + // let pointer = storage::store_function_at_hash(GET_CALLER_EXT, BTreeMap::new()); + // runtime::put_key(GET_CALLER_KEY, pointer.into()); +} diff --git a/smart-contracts/contracts/integration/list-known-urefs-call/Cargo.toml b/smart-contracts/contracts/integration/list-known-urefs-call/Cargo.toml new file mode 100644 index 0000000000..e820f92664 --- /dev/null +++ b/smart-contracts/contracts/integration/list-known-urefs-call/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "list-known-urefs-call" +version = "0.1.0" +authors = ["Mateusz Górski "] +edition = "2018" + +[[bin]] +name = "list_known_urefs_call" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/list-known-urefs-call/src/main.rs b/smart-contracts/contracts/integration/list-known-urefs-call/src/main.rs new file mode 100644 index 0000000000..8a4d8edf12 --- /dev/null +++ b/smart-contracts/contracts/integration/list-known-urefs-call/src/main.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +use contract::{contract_api::runtime, unwrap_or_revert::UnwrapOrRevert}; +use types::{contracts::DEFAULT_ENTRY_POINT_NAME, ApiError, RuntimeArgs}; + +const LIST_NAMED_KEYS_KEY: &str = "list_named_keys"; + +#[no_mangle] +pub extern "C" fn call() { + let contract_hash = runtime::get_key(LIST_NAMED_KEYS_KEY) + .unwrap_or_revert_with(ApiError::GetKey) + .into_hash() + .unwrap_or_revert(); + + // Call `define` part of the contract. + runtime::call_contract( + contract_hash, + DEFAULT_ENTRY_POINT_NAME, + RuntimeArgs::default(), + ) +} diff --git a/smart-contracts/contracts/integration/list-known-urefs-define/Cargo.toml b/smart-contracts/contracts/integration/list-known-urefs-define/Cargo.toml new file mode 100644 index 0000000000..b5985ea6f1 --- /dev/null +++ b/smart-contracts/contracts/integration/list-known-urefs-define/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "list-known-urefs-define" +version = "0.1.0" +authors = ["Mateusz Górski "] +edition = "2018" + +[[bin]] +name = "list_known_urefs_define" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/list-known-urefs-define/src/main.rs b/smart-contracts/contracts/integration/list-known-urefs-define/src/main.rs new file mode 100644 index 0000000000..103ac828d0 --- /dev/null +++ b/smart-contracts/contracts/integration/list-known-urefs-define/src/main.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::borrow::ToOwned; +use core::iter; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{contracts::NamedKeys, ApiError}; + +const BAR_KEY: &str = "Bar"; +const FOO_KEY: &str = "Foo"; +const _LIST_NAMED_KEYS_EXT: &str = "list_named_keys_ext"; +const _LIST_NAMED_KEYS_KEY: &str = "list_named_keys"; +const TEST_UREF: &str = "Test"; + +#[no_mangle] +pub extern "C" fn list_named_keys_ext() { + let passed_in_uref = runtime::get_key(FOO_KEY).unwrap_or_revert_with(ApiError::GetKey); + let uref = storage::new_uref(TEST_UREF); + runtime::put_key(BAR_KEY, uref.clone().into()); + let contracts_named_keys = runtime::list_named_keys(); + let expected_urefs = { + let mut tmp = NamedKeys::new(); + tmp.insert(BAR_KEY.to_owned(), uref.into()); + tmp.insert(FOO_KEY.to_owned(), passed_in_uref); + tmp + }; + // Test that `list_named_keys` returns correct value when in the subcall (contract). + assert_eq!(expected_urefs, contracts_named_keys); +} + +#[no_mangle] +pub extern "C" fn call() { + let uref = storage::new_uref(1i32); + runtime::put_key(FOO_KEY, uref.clone().into()); + let _accounts_named_keys = runtime::list_named_keys(); + let _expected_urefs: NamedKeys = iter::once((FOO_KEY.to_owned(), uref.into())).collect(); + // Test that `list_named_keys` returns correct value when called in the context of an account. + // Store `list_named_keys_ext` to be called in the `call` part of this contract. + // We don't have to pass `expected_urefs` to exercise this function but + // it adds initial known urefs to the state of the contract. + + // TODO: do new style store + // let pointer = storage::store_function_at_hash(LIST_NAMED_KEYS_EXT, expected_urefs); + // runtime::put_key(LIST_NAMED_KEYS_KEY, pointer.into()) +} diff --git a/smart-contracts/contracts/integration/payment-from-named-purse/Cargo.toml b/smart-contracts/contracts/integration/payment-from-named-purse/Cargo.toml new file mode 100644 index 0000000000..993490869b --- /dev/null +++ b/smart-contracts/contracts/integration/payment-from-named-purse/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "payment-from-named-purse" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "payment_from_named_purse" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/payment-from-named-purse/src/main.rs b/smart-contracts/contracts/integration/payment-from-named-purse/src/main.rs new file mode 100644 index 0000000000..5186877420 --- /dev/null +++ b/smart-contracts/contracts/integration/payment-from-named-purse/src/main.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::{ + contract_api::{runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{runtime_args, ApiError, RuntimeArgs, URef, U512}; + +const GET_PAYMENT_PURSE: &str = "get_payment_purse"; +const SET_REFUND_PURSE: &str = "set_refund_purse"; + +#[repr(u16)] +enum Error { + PosNotFound = 1, + NamedPurseNotFound = 2, +} + +impl Into for Error { + fn into(self) -> ApiError { + ApiError::User(self as u16) + } +} + +const ARG_AMOUNT: &str = "amount"; +const ARG_PURSE: &str = "purse"; +const ARG_PURSE_NAME: &str = "purse_name"; + +#[no_mangle] +pub extern "C" fn call() { + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + let name: String = runtime::get_named_arg(ARG_PURSE_NAME); + + // get uref from current context's named_keys + let source = runtime::get_key(&name) + .unwrap_or_revert_with(Error::NamedPurseNotFound) + .into_uref() + .unwrap_or_revert_with(Error::PosNotFound); + + let pos_contract_hash = system::get_proof_of_stake(); + + // set refund purse to source purse + { + let contract_hash = pos_contract_hash; + let runtime_args = runtime_args! { + ARG_PURSE => source, + }; + runtime::call_contract::<()>(contract_hash, SET_REFUND_PURSE, runtime_args); + } + + // fund payment purse + { + let target: URef = + runtime::call_contract(pos_contract_hash, GET_PAYMENT_PURSE, RuntimeArgs::default()); + + system::transfer_from_purse_to_purse(source, target, amount).unwrap_or_revert(); + } +} diff --git a/smart-contracts/contracts/integration/set-key-thresholds/Cargo.toml b/smart-contracts/contracts/integration/set-key-thresholds/Cargo.toml new file mode 100644 index 0000000000..927bfba629 --- /dev/null +++ b/smart-contracts/contracts/integration/set-key-thresholds/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "set-key-thresholds" +version = "0.1.0" +authors = ["Joe Sacher "] +edition = "2018" + +[[bin]] +name = "set_key_thresholds" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/set-key-thresholds/src/main.rs b/smart-contracts/contracts/integration/set-key-thresholds/src/main.rs new file mode 100644 index 0000000000..1e69170f90 --- /dev/null +++ b/smart-contracts/contracts/integration/set-key-thresholds/src/main.rs @@ -0,0 +1,44 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{account, runtime}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + account::{ActionType, Weight}, + ApiError, +}; + +#[repr(u16)] +enum Error { + SetKeyManagementThreshold = 100, + SetDeploymentThreshold = 200, +} + +impl Into for Error { + fn into(self) -> ApiError { + ApiError::User(self as u16) + } +} + +const ARG_KM_WEIGHT: &str = "km_weight"; +const ARG_DEP_WEIGHT: &str = "dep_weight"; + +#[no_mangle] +pub extern "C" fn call() { + let km_weight: u32 = runtime::get_named_arg(ARG_KM_WEIGHT); + let dep_weight: u32 = runtime::get_named_arg(ARG_DEP_WEIGHT); + let key_management_threshold = Weight::new(km_weight as u8); + let deploy_threshold = Weight::new(dep_weight as u8); + + if key_management_threshold != Weight::new(0) { + account::set_action_threshold(ActionType::KeyManagement, key_management_threshold) + .unwrap_or_revert_with(Error::SetKeyManagementThreshold); + } + + if deploy_threshold != Weight::new(0) { + account::set_action_threshold(ActionType::Deployment, deploy_threshold) + .unwrap_or_revert_with(Error::SetDeploymentThreshold); + } +} diff --git a/smart-contracts/contracts/integration/subcall-revert-call/Cargo.toml b/smart-contracts/contracts/integration/subcall-revert-call/Cargo.toml new file mode 100644 index 0000000000..13b1dd0740 --- /dev/null +++ b/smart-contracts/contracts/integration/subcall-revert-call/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "subcall-revert-call" +version = "0.1.0" +authors = ["Piotr Kuchta "] +edition = "2018" + +[[bin]] +name = "subcall_revert_call" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/subcall-revert-call/src/main.rs b/smart-contracts/contracts/integration/subcall-revert-call/src/main.rs new file mode 100644 index 0000000000..42f159f36e --- /dev/null +++ b/smart-contracts/contracts/integration/subcall-revert-call/src/main.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +use contract::{contract_api::runtime, unwrap_or_revert::UnwrapOrRevert}; +use types::{contracts::DEFAULT_ENTRY_POINT_NAME, ApiError, RuntimeArgs}; + +const REVERT_TEST_KEY: &str = "revert_test"; + +#[no_mangle] +pub extern "C" fn call() { + let contract_hash = runtime::get_key(REVERT_TEST_KEY) + .unwrap_or_revert_with(ApiError::GetKey) + .into_hash() + .unwrap_or_revert(); + + runtime::call_contract( + contract_hash, + DEFAULT_ENTRY_POINT_NAME, + RuntimeArgs::default(), + ) +} diff --git a/smart-contracts/contracts/integration/subcall-revert-define/Cargo.toml b/smart-contracts/contracts/integration/subcall-revert-define/Cargo.toml new file mode 100644 index 0000000000..d37e50548b --- /dev/null +++ b/smart-contracts/contracts/integration/subcall-revert-define/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "subcall-revert-define" +version = "0.1.0" +authors = ["Piotr Kuchta "] +edition = "2018" + +[[bin]] +name = "subcall_revert_define" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/subcall-revert-define/src/main.rs b/smart-contracts/contracts/integration/subcall-revert-define/src/main.rs new file mode 100644 index 0000000000..16529fca4e --- /dev/null +++ b/smart-contracts/contracts/integration/subcall-revert-define/src/main.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use contract::contract_api::{runtime, storage}; + +use types::{ + contracts::Parameters, ApiError, CLType, ContractHash, ContractVersion, EntryPoint, + EntryPointAccess, EntryPointType, EntryPoints, +}; + +const ENTRY_POINT_NAME: &str = "revert_test_ext"; +const REVERT_TEST_KEY: &str = "revert_test"; +const REVERT_VERSION_KEY: &str = "revert_version"; + +#[no_mangle] +pub extern "C" fn revert_test_ext() { + // Call revert with an application specific non-zero exit code. + // It is 2 because another contract used by test_revert.py calls revert with 1. + runtime::revert(ApiError::User(2)); +} + +fn store() -> (ContractHash, ContractVersion) { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + ENTRY_POINT_NAME, + Parameters::default(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + storage::new_contract(entry_points, None, None, None) +} + +#[no_mangle] +pub extern "C" fn call() { + let (contract_hash, contract_version) = store(); + let version_uref = storage::new_uref(contract_version); + runtime::put_key(REVERT_VERSION_KEY, version_uref.into()); + runtime::put_key(REVERT_TEST_KEY, contract_hash.into()); +} diff --git a/smart-contracts/contracts/integration/update-associated-key/Cargo.toml b/smart-contracts/contracts/integration/update-associated-key/Cargo.toml new file mode 100644 index 0000000000..dfb42a3880 --- /dev/null +++ b/smart-contracts/contracts/integration/update-associated-key/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "update-associated-key" +version = "0.1.0" +authors = ["Joe Sacher "] +edition = "2018" + +[[bin]] +name = "update_associated_key" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/update-associated-key/src/main.rs b/smart-contracts/contracts/integration/update-associated-key/src/main.rs new file mode 100644 index 0000000000..e9ac8f376e --- /dev/null +++ b/smart-contracts/contracts/integration/update-associated-key/src/main.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{account, runtime}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + account::{AccountHash, Weight}, + ApiError, +}; + +#[repr(u16)] +enum Error { + UpdateAssociatedKey = 100, +} + +impl Into for Error { + fn into(self) -> ApiError { + ApiError::User(self as u16) + } +} + +const ARG_ACCOUNT: &str = "account"; +const ARG_WEIGHT: &str = "weight"; + +#[no_mangle] +pub extern "C" fn call() { + let account: AccountHash = runtime::get_named_arg(ARG_ACCOUNT); + let weight_val: u32 = runtime::get_named_arg(ARG_WEIGHT); + let weight = Weight::new(weight_val as u8); + + account::update_associated_key(account, weight) + .unwrap_or_revert_with(Error::UpdateAssociatedKey); +} diff --git a/smart-contracts/contracts/integration/write-all-types/Cargo.toml b/smart-contracts/contracts/integration/write-all-types/Cargo.toml new file mode 100644 index 0000000000..cd6fb50910 --- /dev/null +++ b/smart-contracts/contracts/integration/write-all-types/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "write-all-types" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "write_all_types" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/integration/write-all-types/src/main.rs b/smart-contracts/contracts/integration/write-all-types/src/main.rs new file mode 100644 index 0000000000..045bd9d1d0 --- /dev/null +++ b/smart-contracts/contracts/integration/write-all-types/src/main.rs @@ -0,0 +1,99 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate alloc; + +use alloc::{collections::BTreeMap, string::String, vec::Vec}; +use contract::contract_api::{runtime, storage}; +use types::{ + contracts::Parameters, CLType, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Key, + URef, U128, U256, U512, +}; + +#[no_mangle] +pub extern "C" fn do_nothing() {} + +#[no_mangle] +#[allow(clippy::let_unit_value, clippy::unit_arg)] +pub extern "C" fn call() { + let v01: bool = true; + let v02: i32 = 2; + let v03: i64 = 3; + let v04: u8 = 4; + let v05: u32 = 5; + let v06: u64 = 6; + let v07: U128 = U128::from(7); + let v08: U256 = U256::from(8); + let v09: U512 = U512::from(9); + let v10: () = (); + let v11: String = String::from("Hello, World"); + let v12: Key = Key::Hash([12u8; 32]); + let v13: URef = storage::new_uref(()); + let v14: Option = Some(14); + let v15: Vec = vec![String::from("ABCD"), String::from("EFG")]; + let v16: [Option; 4] = [None, Some(0), Some(1), None]; + let v17: Result = Ok(U512::from(17)); + let v18: BTreeMap = vec![(0, false), (1, true), (3, true)].into_iter().collect(); + let v19: (u64,) = (19,); + let v20: (u8, u32) = (0, 1); + let v21: (u8, u32, u64) = (0, 1, 2); + + let u001 = storage::new_uref(v01); + let u002 = storage::new_uref(v02); + let u003 = storage::new_uref(v03); + let u004 = storage::new_uref(v04); + let u005 = storage::new_uref(v05); + let u006 = storage::new_uref(v06); + let u007 = storage::new_uref(v07); + let u008 = storage::new_uref(v08); + let u009 = storage::new_uref(v09); + let u010 = storage::new_uref(v10); + let u011 = storage::new_uref(v11); + let u012 = storage::new_uref(v12); + let u013 = storage::new_uref(v13); + let u014 = storage::new_uref(v14); + let u015 = storage::new_uref(v15); + let u016 = storage::new_uref(v16); + let u017 = storage::new_uref(v17); + let u018 = storage::new_uref(v18); + let u019 = storage::new_uref(v19); + let u020 = storage::new_uref(v20); + let u021 = storage::new_uref(v21); + let u022 = { + let mut entry_points = EntryPoints::new(); + entry_points.add_entry_point(EntryPoint::new( + "do_nothing", + Parameters::new(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + )); + let (contract_hash, _contract_version) = + storage::new_contract(entry_points, None, None, None); + contract_hash + }; + + runtime::put_key("v01", u001.into()); + runtime::put_key("v02", u002.into()); + runtime::put_key("v03", u003.into()); + runtime::put_key("v04", u004.into()); + runtime::put_key("v05", u005.into()); + runtime::put_key("v06", u006.into()); + runtime::put_key("v07", u007.into()); + runtime::put_key("v08", u008.into()); + runtime::put_key("v09", u009.into()); + runtime::put_key("v10", u010.into()); + runtime::put_key("v11", u011.into()); + runtime::put_key("v12", u012.into()); + runtime::put_key("v13", u013.into()); + runtime::put_key("v14", u014.into()); + runtime::put_key("v15", u015.into()); + runtime::put_key("v16", u016.into()); + runtime::put_key("v17", u017.into()); + runtime::put_key("v18", u018.into()); + runtime::put_key("v19", u019.into()); + runtime::put_key("v20", u020.into()); + runtime::put_key("v21", u021.into()); + runtime::put_key("v22", u022.into()); +} diff --git a/smart-contracts/contracts/profiling/host-function-metrics/Cargo.toml b/smart-contracts/contracts/profiling/host-function-metrics/Cargo.toml new file mode 100644 index 0000000000..d4dabfdc78 --- /dev/null +++ b/smart-contracts/contracts/profiling/host-function-metrics/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "host-function-metrics" +version = "0.1.0" +authors = ["Fraser Hutchison "] +edition = "2018" + +[lib] +crate-type = ["cdylib"] +bench = false +doctest = false +test = false + +[features] +default = ["contract/test-support", "rand/small_rng"] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } +rand = { version = "0.7", default-features = false } diff --git a/smart-contracts/contracts/profiling/host-function-metrics/src/lib.rs b/smart-contracts/contracts/profiling/host-function-metrics/src/lib.rs new file mode 100644 index 0000000000..fd50d16302 --- /dev/null +++ b/smart-contracts/contracts/profiling/host-function-metrics/src/lib.rs @@ -0,0 +1,676 @@ +#![no_std] + +extern crate alloc; + +use alloc::{boxed::Box, string::String, vec, vec::Vec}; +use core::iter::{self, FromIterator}; + +use rand::{distributions::Alphanumeric, rngs::SmallRng, Rng, SeedableRng}; + +use contract::{ + contract_api::{account, runtime, storage, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + account::{AccountHash, ActionType, Weight}, + contracts::NamedKeys, + runtime_args, ApiError, BlockTime, CLType, CLValue, ContractHash, ContractVersion, EntryPoint, + EntryPointAccess, EntryPointType, EntryPoints, Key, Parameter, Phase, RuntimeArgs, U512, +}; + +const MIN_FUNCTION_NAME_LENGTH: usize = 1; +const MAX_FUNCTION_NAME_LENGTH: usize = 100; + +const NAMED_KEY_COUNT: usize = 100; +const MIN_NAMED_KEY_NAME_LENGTH: usize = 10; +// TODO - consider increasing to e.g. 1_000 once https://casperlabs.atlassian.net/browse/EE-966 is +// resolved. +const MAX_NAMED_KEY_NAME_LENGTH: usize = 100; +const VALUE_FOR_ADDITION_1: u64 = 1; +const VALUE_FOR_ADDITION_2: u64 = 2; +const TRANSFER_AMOUNT: u64 = 1_000_000; + +const ARG_SEED: &str = "seed"; +const ARG_OTHERS: &str = "others"; +const ARG_BYTES: &str = "bytes"; + +#[repr(u16)] +enum Error { + GetCaller = 0, + GetBlockTime = 1, + GetPhase = 2, + HasKey = 3, + GetKey = 4, + NamedKeys = 5, + ReadOrRevert = 6, + ReadLocal = 7, + IsValidURef = 8, + Transfer = 9, + Revert = 10, +} + +impl From for ApiError { + fn from(error: Error) -> ApiError { + ApiError::User(error as u16) + } +} + +fn create_random_names<'a>(rng: &'a mut SmallRng) -> impl Iterator + 'a { + iter::repeat_with(move || { + let key_length: usize = rng.gen_range(MIN_NAMED_KEY_NAME_LENGTH, MAX_NAMED_KEY_NAME_LENGTH); + rng.sample_iter(&Alphanumeric) + .take(key_length) + .collect::() + }) + .take(NAMED_KEY_COUNT) +} + +fn truncate_named_keys(named_keys: NamedKeys, rng: &mut SmallRng) -> NamedKeys { + let truncated_len = rng.gen_range(1, named_keys.len() + 1); + let mut vec = named_keys.into_iter().collect::>(); + vec.truncate(truncated_len); + vec.into_iter().collect() +} + +// Executes the named key functions from the `runtime` module and most of the functions from the +// `storage` module. +fn large_function() { + let seed: u64 = runtime::get_named_arg(ARG_SEED); + let random_bytes: Vec = runtime::get_named_arg(ARG_BYTES); + + let uref = storage::new_uref(random_bytes.clone()); + + let mut rng = SmallRng::seed_from_u64(seed); + let mut key_name = String::new(); + for random_name in create_random_names(&mut rng) { + key_name = random_name; + runtime::put_key(&key_name, Key::from(uref)); + } + + if !runtime::has_key(&key_name) { + runtime::revert(Error::HasKey); + } + + if runtime::get_key(&key_name) != Some(Key::from(uref)) { + runtime::revert(Error::GetKey); + } + + runtime::remove_key(&key_name); + + let named_keys = runtime::list_named_keys(); + if named_keys.len() != NAMED_KEY_COUNT - 1 { + runtime::revert(Error::NamedKeys) + } + + storage::write(uref, random_bytes.clone()); + let retrieved_value: Vec = storage::read_or_revert(uref); + if retrieved_value != random_bytes { + runtime::revert(Error::ReadOrRevert); + } + + storage::write(uref, VALUE_FOR_ADDITION_1); + storage::add(uref, VALUE_FOR_ADDITION_2); + + storage::write_local(key_name.clone(), random_bytes.clone()); + let retrieved_value = storage::read_local(&key_name); + if retrieved_value != Ok(Some(random_bytes)) { + runtime::revert(Error::ReadLocal); + } + + storage::write_local(key_name.clone(), VALUE_FOR_ADDITION_1); + storage::add_local(key_name, VALUE_FOR_ADDITION_2); + + let keys_to_return = truncate_named_keys(named_keys, &mut rng); + runtime::ret(CLValue::from_t(keys_to_return).unwrap_or_revert()); +} + +fn small_function() { + if runtime::get_phase() != Phase::Session { + runtime::revert(Error::GetPhase); + } +} + +#[no_mangle] +pub extern "C" fn call() { + let seed: u64 = runtime::get_named_arg(ARG_SEED); + let (random_bytes, source_account, destination_account): (Vec, AccountHash, AccountHash) = + runtime::get_named_arg(ARG_OTHERS); + + // ========== storage, execution and upgrading of contracts ==================================== + + // Store large function with no named keys, then execute it to get named keys returned. + let mut rng = SmallRng::seed_from_u64(seed); + let large_function_name = String::from_iter( + iter::repeat('l') + .take(rng.gen_range(MIN_FUNCTION_NAME_LENGTH, MAX_FUNCTION_NAME_LENGTH + 1)), + ); + + let entry_point_name = &large_function_name; + let runtime_args = runtime_args! { + ARG_SEED => seed, + ARG_BYTES => random_bytes.clone() + }; + + let (contract_hash, _contract_version) = store_function(entry_point_name, None); + let named_keys: NamedKeys = + runtime::call_contract(contract_hash, entry_point_name, runtime_args.clone()); + + let (contract_hash, _contract_version) = + store_function(entry_point_name, Some(named_keys.clone())); + // Store large function with 10 named keys, then execute it. + runtime::call_contract::(contract_hash, entry_point_name, runtime_args); + + // Small function + let small_function_name = String::from_iter( + iter::repeat('s') + .take(rng.gen_range(MIN_FUNCTION_NAME_LENGTH, MAX_FUNCTION_NAME_LENGTH + 1)), + ); + + let entry_point_name = &small_function_name; + let runtime_args = runtime_args! {}; + + // Store small function with no named keys, then execute it. + let (contract_hash, _contract_version) = + store_function(entry_point_name, Some(NamedKeys::new())); + runtime::call_contract::<()>(contract_hash, entry_point_name, runtime_args.clone()); + + let (contract_hash, _contract_version) = store_function(entry_point_name, Some(named_keys)); + // Store small function with 10 named keys, then execute it. + runtime::call_contract::<()>(contract_hash, entry_point_name, runtime_args); + + // ========== functions from `account` module ================================================== + + let main_purse = account::get_main_purse(); + account::set_action_threshold(ActionType::Deployment, Weight::new(1)).unwrap_or_revert(); + account::add_associated_key(destination_account, Weight::new(1)).unwrap_or_revert(); + account::update_associated_key(destination_account, Weight::new(1)).unwrap_or_revert(); + account::remove_associated_key(destination_account).unwrap_or_revert(); + + // ========== functions from `system` module =================================================== + + let _ = system::get_mint(); + + let new_purse = system::create_purse(); + + let transfer_amount = U512::from(TRANSFER_AMOUNT); + system::transfer_from_purse_to_purse(main_purse, new_purse, transfer_amount).unwrap_or_revert(); + + let balance = system::get_balance(new_purse).unwrap_or_revert(); + if balance != transfer_amount { + runtime::revert(Error::Transfer); + } + + system::transfer_from_purse_to_account(new_purse, destination_account, transfer_amount) + .unwrap_or_revert(); + + system::transfer_to_account(destination_account, transfer_amount).unwrap_or_revert(); + + // ========== remaining functions from `runtime` module ======================================== + + if !runtime::is_valid_uref(main_purse) { + runtime::revert(Error::IsValidURef); + } + + if runtime::get_blocktime() != BlockTime::new(0) { + runtime::revert(Error::GetBlockTime); + } + + if runtime::get_caller() != source_account { + runtime::revert(Error::GetCaller); + } + + runtime::print(&String::from_utf8_lossy(&random_bytes)); + + runtime::revert(Error::Revert); +} + +fn store_function( + entry_point_name: &str, + named_keys: Option, +) -> (ContractHash, ContractVersion) { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + entry_point_name, + vec![ + Parameter::new(ARG_SEED, CLType::U64), + Parameter::new(ARG_BYTES, CLType::List(Box::new(CLType::U8))), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + storage::new_contract(entry_points, named_keys, None, None) +} + +#[rustfmt::skip] #[no_mangle] pub extern "C" fn s() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn sssssssssssssssssssssssssss() { small_function() +} +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" +fn ssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern +"C" fn sssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub +extern "C" fn ssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn sssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub +extern "C" fn ssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn sssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub +extern "C" fn ssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn sssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] +pub extern "C" fn ssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn sssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn sssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn sssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn sssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn sssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn sssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn sssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() +} +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { small_function() +} +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() { +small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() +{ small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() +{ small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() +{ small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() +{ small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() +{ small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() +{ small_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss() +{ small_function() } + +#[rustfmt::skip] #[no_mangle] pub extern "C" fn l() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn ll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn lllllllllllllllllllllllllll() { large_function() +} +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" +fn llllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern +"C" fn lllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub +extern "C" fn llllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn lllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub +extern "C" fn llllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn lllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub +extern "C" fn llllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn lllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn llllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] +pub extern "C" fn llllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn lllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn lllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn lllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn lllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn lllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn lllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] +#[no_mangle] pub extern "C" fn lllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt:: +skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() +} +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { large_function() +} +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() { +large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() +{ large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() +{ large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() +{ large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() +{ large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() +{ large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() +{ large_function() } +#[rustfmt::skip] #[no_mangle] pub extern "C" fn +llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll() +{ large_function() } diff --git a/smart-contracts/contracts/profiling/simple-transfer/Cargo.toml b/smart-contracts/contracts/profiling/simple-transfer/Cargo.toml new file mode 100644 index 0000000000..ff31e34147 --- /dev/null +++ b/smart-contracts/contracts/profiling/simple-transfer/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "simple-transfer" +version = "0.1.0" +authors = ["Fraser Hutchison "] +edition = "2018" + +[[bin]] +name = "simple_transfer" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/profiling/simple-transfer/src/main.rs b/smart-contracts/contracts/profiling/simple-transfer/src/main.rs new file mode 100644 index 0000000000..bacd1df8c8 --- /dev/null +++ b/smart-contracts/contracts/profiling/simple-transfer/src/main.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{account::AccountHash, ApiError, TransferredTo, U512}; + +const ARG_ACCOUNT_HASH: &str = "account_hash"; +const ARG_AMOUNT: &str = "amount"; + +#[repr(u16)] +enum Error { + NonExistentAccount = 0, +} + +#[no_mangle] +pub extern "C" fn call() { + let account_hash: AccountHash = runtime::get_named_arg(ARG_ACCOUNT_HASH); + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + match system::transfer_to_account(account_hash, amount).unwrap_or_revert() { + TransferredTo::NewAccount => { + runtime::revert(ApiError::User(Error::NonExistentAccount as u16)) + } + TransferredTo::ExistingAccount => (), + } +} diff --git a/smart-contracts/contracts/profiling/state-initializer/Cargo.toml b/smart-contracts/contracts/profiling/state-initializer/Cargo.toml new file mode 100644 index 0000000000..f90100201d --- /dev/null +++ b/smart-contracts/contracts/profiling/state-initializer/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "state-initializer" +version = "0.1.0" +authors = ["Fraser Hutchison "] +edition = "2018" + +[[bin]] +name = "state_initializer" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/profiling/state-initializer/src/main.rs b/smart-contracts/contracts/profiling/state-initializer/src/main.rs new file mode 100644 index 0000000000..11f7b555f4 --- /dev/null +++ b/smart-contracts/contracts/profiling/state-initializer/src/main.rs @@ -0,0 +1,36 @@ +//! Transfers the requested amount of motes to the first account and zero motes to the second +//! account. +#![no_std] +#![no_main] + +use contract::contract_api::{runtime, system}; +use types::{account::AccountHash, ApiError, TransferredTo, U512}; + +const ARG_ACCOUNT1_ACCOUNT_HASH: &str = "account_1_account_hash"; +const ARG_ACCOUNT1_AMOUNT: &str = "account_1_amount"; +const ARG_ACCOUNT2_ACCOUNT_HASH: &str = "account_2_account_hash"; + +#[repr(u16)] +enum Error { + AccountAlreadyExists = 0, +} + +fn create_account_with_amount(account: AccountHash, amount: U512) { + match system::transfer_to_account(account, amount) { + Ok(TransferredTo::NewAccount) => (), + Ok(TransferredTo::ExistingAccount) => { + runtime::revert(ApiError::User(Error::AccountAlreadyExists as u16)) + } + Err(_) => runtime::revert(ApiError::Transfer), + } +} + +#[no_mangle] +pub extern "C" fn call() { + let account_hash1: AccountHash = runtime::get_named_arg(ARG_ACCOUNT1_ACCOUNT_HASH); + let amount: U512 = runtime::get_named_arg(ARG_ACCOUNT1_AMOUNT); + create_account_with_amount(account_hash1, amount); + + let account_hash2: AccountHash = runtime::get_named_arg(ARG_ACCOUNT2_ACCOUNT_HASH); + create_account_with_amount(account_hash2, U512::zero()); +} diff --git a/smart-contracts/contracts/system/mint-install/Cargo.toml b/smart-contracts/contracts/system/mint-install/Cargo.toml new file mode 100644 index 0000000000..b10e7977d1 --- /dev/null +++ b/smart-contracts/contracts/system/mint-install/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "mint-install" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "mint_install" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } +mint-token = { path = "../mint-token" } diff --git a/smart-contracts/contracts/system/mint-install/src/main.rs b/smart-contracts/contracts/system/mint-install/src/main.rs new file mode 100644 index 0000000000..72a827b190 --- /dev/null +++ b/smart-contracts/contracts/system/mint-install/src/main.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{contracts::NamedKeys, CLValue}; + +const HASH_KEY_NAME: &str = "mint_hash"; +const ACCESS_KEY_NAME: &str = "mint_access"; + +#[no_mangle] +pub extern "C" fn mint() { + mint_token::mint(); +} + +#[no_mangle] +pub extern "C" fn create() { + mint_token::create(); +} + +#[no_mangle] +pub extern "C" fn balance() { + mint_token::balance(); +} + +#[no_mangle] +pub extern "C" fn transfer() { + mint_token::transfer(); +} + +#[no_mangle] +pub extern "C" fn install() { + let entry_points = mint_token::get_entry_points(); + + let (contract_package_hash, access_uref) = storage::create_contract_package_at_hash(); + runtime::put_key(HASH_KEY_NAME, contract_package_hash.into()); + runtime::put_key(ACCESS_KEY_NAME, access_uref.into()); + + let named_keys = NamedKeys::new(); + + let (contract_key, _contract_version) = + storage::add_contract_version(contract_package_hash, entry_points, named_keys); + + let return_value = CLValue::from_t((contract_package_hash, contract_key)).unwrap_or_revert(); + runtime::ret(return_value); +} diff --git a/smart-contracts/contracts/system/mint-token/Cargo.toml b/smart-contracts/contracts/system/mint-token/Cargo.toml new file mode 100644 index 0000000000..55f3929ec9 --- /dev/null +++ b/smart-contracts/contracts/system/mint-token/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mint-token" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "mint_token" +path = "src/bin/main.rs" +bench = false +doctest = false +test = false + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/system/mint-token/src/bin/main.rs b/smart-contracts/contracts/system/mint-token/src/bin/main.rs new file mode 100644 index 0000000000..11a8412d71 --- /dev/null +++ b/smart-contracts/contracts/system/mint-token/src/bin/main.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +#[no_mangle] +pub extern "C" fn mint() { + mint_token::mint(); +} + +#[no_mangle] +pub extern "C" fn create() { + mint_token::create(); +} + +#[no_mangle] +pub extern "C" fn balance() { + mint_token::balance(); +} + +#[no_mangle] +pub extern "C" fn transfer() { + mint_token::transfer(); +} diff --git a/smart-contracts/contracts/system/mint-token/src/lib.rs b/smart-contracts/contracts/system/mint-token/src/lib.rs new file mode 100644 index 0000000000..ccf9c27e55 --- /dev/null +++ b/smart-contracts/contracts/system/mint-token/src/lib.rs @@ -0,0 +1,160 @@ +#![no_std] + +#[macro_use] +extern crate alloc; + +use alloc::boxed::Box; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + account::AccountHash, + bytesrepr::{FromBytes, ToBytes}, + contracts::Parameters, + mint::{Mint, RuntimeProvider, StorageProvider}, + system_contract_errors::mint::Error, + CLType, CLTyped, CLValue, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Key, + Parameter, URef, U512, +}; + +pub const METHOD_MINT: &str = "mint"; +pub const METHOD_CREATE: &str = "create"; +pub const METHOD_BALANCE: &str = "balance"; +pub const METHOD_TRANSFER: &str = "transfer"; + +pub const ARG_AMOUNT: &str = "amount"; +pub const ARG_PURSE: &str = "purse"; +pub const ARG_SOURCE: &str = "source"; +pub const ARG_TARGET: &str = "target"; + +pub struct MintContract; + +impl RuntimeProvider for MintContract { + fn get_caller(&self) -> AccountHash { + runtime::get_caller() + } + + fn put_key(&mut self, name: &str, key: Key) { + runtime::put_key(name, key) + } +} + +impl StorageProvider for MintContract { + fn new_uref(&mut self, init: T) -> URef { + storage::new_uref(init) + } + + fn write_local(&mut self, key: K, value: V) { + storage::write_local(key, value) + } + + fn read_local( + &mut self, + key: &K, + ) -> Result, Error> { + storage::read_local(key).map_err(|_| Error::Storage) + } + + fn read(&mut self, uref: URef) -> Result, Error> { + storage::read(uref).map_err(|_| Error::Storage) + } + + fn write(&mut self, uref: URef, value: T) -> Result<(), Error> { + storage::write(uref, value); + Ok(()) + } + + fn add(&mut self, uref: URef, value: T) -> Result<(), Error> { + storage::add(uref, value); + Ok(()) + } +} + +impl Mint for MintContract {} + +pub fn mint() { + let mut mint_contract = MintContract; + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + let result: Result = mint_contract.mint(amount); + let ret = CLValue::from_t(result).unwrap_or_revert(); + runtime::ret(ret) +} + +pub fn create() { + let mut mint_contract = MintContract; + let uref = mint_contract.mint(U512::zero()).unwrap_or_revert(); + let ret = CLValue::from_t(uref).unwrap_or_revert(); + runtime::ret(ret) +} + +pub fn balance() { + let mut mint_contract = MintContract; + let uref: URef = runtime::get_named_arg(ARG_PURSE); + let balance: Option = mint_contract.balance(uref).unwrap_or_revert(); + let ret = CLValue::from_t(balance).unwrap_or_revert(); + runtime::ret(ret) +} + +pub fn transfer() { + let mut mint_contract = MintContract; + let source: URef = runtime::get_named_arg(ARG_SOURCE); + let target: URef = runtime::get_named_arg(ARG_TARGET); + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + let result: Result<(), Error> = mint_contract.transfer(source, target, amount); + let ret = CLValue::from_t(result).unwrap_or_revert(); + runtime::ret(ret); +} + +pub fn get_entry_points() -> EntryPoints { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + METHOD_MINT, + vec![Parameter::new(ARG_AMOUNT, CLType::U512)], + CLType::Result { + ok: Box::new(CLType::URef), + err: Box::new(CLType::U8), + }, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + + let entry_point = EntryPoint::new( + METHOD_CREATE, + Parameters::new(), + CLType::URef, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + + let entry_point = EntryPoint::new( + METHOD_BALANCE, + vec![Parameter::new(ARG_PURSE, CLType::URef)], + CLType::Option(Box::new(CLType::U512)), + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + + let entry_point = EntryPoint::new( + METHOD_TRANSFER, + vec![ + Parameter::new(ARG_SOURCE, CLType::URef), + Parameter::new(ARG_TARGET, CLType::URef), + Parameter::new(ARG_AMOUNT, CLType::U512), + ], + CLType::Result { + ok: Box::new(CLType::Unit), + err: Box::new(CLType::U8), + }, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + + entry_points +} diff --git a/smart-contracts/contracts/system/pos-install/Cargo.toml b/smart-contracts/contracts/system/pos-install/Cargo.toml new file mode 100644 index 0000000000..220dc4298a --- /dev/null +++ b/smart-contracts/contracts/system/pos-install/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "pos-install" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "pos_install" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] +enable-bonding = ["pos/enable-bonding"] + +[dependencies] +base16 = { version = "0.2.1", default-features = false } +contract = { path = "../../../contract", package = "casperlabs-contract" } +pos = { path = "../pos" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/system/pos-install/src/main.rs b/smart-contracts/contracts/system/pos-install/src/main.rs new file mode 100644 index 0000000000..430738c90c --- /dev/null +++ b/smart-contracts/contracts/system/pos-install/src/main.rs @@ -0,0 +1,190 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{collections::BTreeMap, string::String, vec}; + +use alloc::{boxed::Box, string::ToString}; +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use pos::{ + ARG_ACCOUNT_KEY, ARG_AMOUNT, ARG_PURSE, METHOD_BOND, METHOD_FINALIZE_PAYMENT, + METHOD_GET_PAYMENT_PURSE, METHOD_GET_REFUND_PURSE, METHOD_SET_REFUND_PURSE, METHOD_UNBOND, +}; +use types::proof_of_stake::Stakes; +use types::{ + account::AccountHash, + contracts::{ + EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, NamedKeys, Parameter, + CONTRACT_INITIAL_VERSION, + }, + runtime_args, + system_contract_errors::mint, + CLType, CLValue, ContractPackageHash, Key, RuntimeArgs, URef, U512, +}; + +const PLACEHOLDER_KEY: Key = Key::Hash([0u8; 32]); +const POS_BONDING_PURSE: &str = "pos_bonding_purse"; +const POS_PAYMENT_PURSE: &str = "pos_payment_purse"; +const POS_REWARDS_PURSE: &str = "pos_rewards_purse"; + +const ARG_MINT_PACKAGE_HASH: &str = "mint_contract_package_hash"; +const ARG_GENESIS_VALIDATORS: &str = "genesis_validators"; +const ENTRY_POINT_MINT: &str = "mint"; + +const HASH_KEY_NAME: &str = "pos_hash"; +const ACCESS_KEY_NAME: &str = "pos_access"; + +#[no_mangle] +pub extern "C" fn bond() { + pos::bond(); +} + +#[no_mangle] +pub extern "C" fn unbond() { + pos::unbond(); +} + +#[no_mangle] +pub extern "C" fn get_payment_purse() { + pos::get_payment_purse(); +} + +#[no_mangle] +pub extern "C" fn set_refund_purse() { + pos::set_refund_purse(); +} + +#[no_mangle] +pub extern "C" fn get_refund_purse() { + pos::get_refund_purse(); +} + +#[no_mangle] +pub extern "C" fn finalize_payment() { + pos::finalize_payment(); +} + +#[no_mangle] +pub extern "C" fn install() { + let mint_package_hash: ContractPackageHash = runtime::get_named_arg(ARG_MINT_PACKAGE_HASH); + let genesis_validators: BTreeMap = + runtime::get_named_arg(ARG_GENESIS_VALIDATORS); + + let stakes = Stakes::new(genesis_validators); + + // Add genesis validators to PoS contract object. + // For now, we are storing validators in `named_keys` map of the PoS contract + // in the form: key: "v_{validator_pk}_{validator_stake}", value: doesn't + // matter. + let mut named_keys: NamedKeys = stakes.strings().map(|key| (key, PLACEHOLDER_KEY)).collect(); + let total_bonds: U512 = stakes.total_bonds(); + let bonding_purse = mint_purse(mint_package_hash, total_bonds); + let payment_purse = mint_purse(mint_package_hash, U512::zero()); + let rewards_purse = mint_purse(mint_package_hash, U512::zero()); + + // Include PoS purses in its named_keys + [ + (POS_BONDING_PURSE, bonding_purse), + (POS_PAYMENT_PURSE, payment_purse), + (POS_REWARDS_PURSE, rewards_purse), + ] + .iter() + .for_each(|(name, uref)| { + named_keys.insert(String::from(*name), Key::URef(*uref)); + }); + + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let bond = EntryPoint::new( + METHOD_BOND.to_string(), + vec![ + Parameter::new(ARG_AMOUNT, CLType::U512), + Parameter::new(ARG_PURSE, CLType::URef), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(bond); + + let unbond = EntryPoint::new( + METHOD_UNBOND.to_string(), + vec![Parameter::new(ARG_AMOUNT, CLType::U512)], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(unbond); + + let get_payment_purse = EntryPoint::new( + METHOD_GET_PAYMENT_PURSE.to_string(), + vec![], + CLType::URef, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(get_payment_purse); + + let set_refund_purse = EntryPoint::new( + METHOD_SET_REFUND_PURSE.to_string(), + vec![Parameter::new(ARG_PURSE, CLType::URef)], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(set_refund_purse); + + let get_refund_purse = EntryPoint::new( + METHOD_GET_REFUND_PURSE.to_string(), + vec![], + CLType::Option(Box::new(CLType::URef)), + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(get_refund_purse); + + let finalize_payment = EntryPoint::new( + METHOD_FINALIZE_PAYMENT.to_string(), + vec![ + Parameter::new(ARG_AMOUNT, CLType::U512), + Parameter::new(ARG_ACCOUNT_KEY, CLType::FixedList(Box::new(CLType::U8), 32)), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(finalize_payment); + + entry_points + }; + + let (contract_package_hash, access_uref) = storage::create_contract_package_at_hash(); + runtime::put_key(HASH_KEY_NAME, contract_package_hash.into()); + runtime::put_key(ACCESS_KEY_NAME, access_uref.into()); + + let (contract_key, _contract_version) = + storage::add_contract_version(contract_package_hash, entry_points, named_keys); + + let return_value = CLValue::from_t((contract_package_hash, contract_key)).unwrap_or_revert(); + runtime::ret(return_value); +} + +fn mint_purse(contract_package_hash: ContractPackageHash, amount: U512) -> URef { + let args = runtime_args! { + ARG_AMOUNT => amount, + }; + + let result: Result = runtime::call_versioned_contract( + contract_package_hash, + Some(CONTRACT_INITIAL_VERSION), + ENTRY_POINT_MINT, + args, + ); + + result.unwrap_or_revert() +} diff --git a/smart-contracts/contracts/system/pos/Cargo.toml b/smart-contracts/contracts/system/pos/Cargo.toml new file mode 100644 index 0000000000..a02bdc535b --- /dev/null +++ b/smart-contracts/contracts/system/pos/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "pos" +version = "0.1.0" +authors = ["Andreas Fackler "] +edition = "2018" + +[features] +std = ["contract/std", "types/std"] +enable-bonding = [] + +[dependencies] +base16 = { version = "0.2.1", default-features = false } +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/system/pos/src/bin/main.rs b/smart-contracts/contracts/system/pos/src/bin/main.rs new file mode 100644 index 0000000000..a2f602e8fe --- /dev/null +++ b/smart-contracts/contracts/system/pos/src/bin/main.rs @@ -0,0 +1,32 @@ +#![no_std] +#![cfg_attr(not(test), no_main)] + +#[no_mangle] +pub extern "C" fn bond() { + pos::bond(); +} + +#[no_mangle] +pub extern "C" fn unbond() { + pos::unbond(); +} + +#[no_mangle] +pub extern "C" fn get_payment_purse() { + pos::get_payment_purse(); +} + +#[no_mangle] +pub extern "C" fn set_refund_purse() { + pos::set_refund_purse(); +} + +#[no_mangle] +pub extern "C" fn get_refund_purse() { + pos::get_refund_purse(); +} + +#[no_mangle] +pub extern "C" fn finalize_payment() { + pos::finalize_payment(); +} diff --git a/smart-contracts/contracts/system/pos/src/lib.rs b/smart-contracts/contracts/system/pos/src/lib.rs new file mode 100644 index 0000000000..ce8959b6bc --- /dev/null +++ b/smart-contracts/contracts/system/pos/src/lib.rs @@ -0,0 +1,228 @@ +#![cfg_attr(not(test), no_std)] + +extern crate alloc; + +use alloc::{ + collections::{BTreeMap, BTreeSet}, + string::String, +}; + +use contract::{ + contract_api::{runtime, storage, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::proof_of_stake::{ + MintProvider, ProofOfStake, Queue, QueueProvider, RuntimeProvider, Stakes, StakesProvider, +}; +use types::{ + account::AccountHash, system_contract_errors::pos::Error, ApiError, BlockTime, CLValue, Key, + Phase, TransferResult, URef, U512, +}; + +pub const METHOD_BOND: &str = "bond"; +pub const METHOD_UNBOND: &str = "unbond"; +pub const METHOD_GET_PAYMENT_PURSE: &str = "get_payment_purse"; +pub const METHOD_SET_REFUND_PURSE: &str = "set_refund_purse"; +pub const METHOD_GET_REFUND_PURSE: &str = "get_refund_purse"; +pub const METHOD_FINALIZE_PAYMENT: &str = "finalize_payment"; + +const BONDING_KEY: u8 = 1; +const UNBONDING_KEY: u8 = 2; + +pub const ARG_AMOUNT: &str = "amount"; +pub const ARG_PURSE: &str = "purse"; +pub const ARG_ACCOUNT_KEY: &str = "account"; + +pub struct ProofOfStakeContract; + +impl MintProvider for ProofOfStakeContract { + fn transfer_purse_to_account( + &mut self, + source: URef, + target: AccountHash, + amount: U512, + ) -> TransferResult { + system::transfer_from_purse_to_account(source, target, amount) + } + + fn transfer_purse_to_purse( + &mut self, + source: URef, + target: URef, + amount: U512, + ) -> Result<(), ()> { + system::transfer_from_purse_to_purse(source, target, amount).map_err(|_| ()) + } + + fn balance(&mut self, purse: URef) -> Option { + system::get_balance(purse) + } +} + +impl QueueProvider for ProofOfStakeContract { + /// Reads bonding queue from the local state of the contract. + fn read_bonding(&mut self) -> Queue { + storage::read_local(&BONDING_KEY) + .unwrap_or_default() + .unwrap_or_default() + } + + /// Reads unbonding queue from the local state of the contract. + fn read_unbonding(&mut self) -> Queue { + storage::read_local(&UNBONDING_KEY) + .unwrap_or_default() + .unwrap_or_default() + } + + /// Writes bonding queue to the local state of the contract. + fn write_bonding(&mut self, queue: Queue) { + storage::write_local(BONDING_KEY, queue); + } + + /// Writes unbonding queue to the local state of the contract. + fn write_unbonding(&mut self, queue: Queue) { + storage::write_local(UNBONDING_KEY, queue); + } +} + +impl RuntimeProvider for ProofOfStakeContract { + fn get_key(&self, name: &str) -> Option { + runtime::get_key(name) + } + + fn put_key(&mut self, name: &str, key: Key) { + runtime::put_key(name, key) + } + + fn remove_key(&mut self, name: &str) { + runtime::remove_key(name) + } + + fn get_phase(&self) -> Phase { + runtime::get_phase() + } + + fn get_block_time(&self) -> BlockTime { + runtime::get_blocktime() + } + + fn get_caller(&self) -> AccountHash { + runtime::get_caller() + } +} + +impl StakesProvider for ProofOfStakeContract { + /// Reads the current stakes from the contract's known urefs. + fn read(&self) -> Result { + let mut stakes = BTreeMap::new(); + for (name, _) in runtime::list_named_keys() { + let mut split_name = name.split('_'); + if Some("v") != split_name.next() { + continue; + } + let hex_key = split_name + .next() + .ok_or(Error::StakesKeyDeserializationFailed)?; + if hex_key.len() != 64 { + return Err(Error::StakesKeyDeserializationFailed); + } + let mut key_bytes = [0u8; 32]; + let _bytes_written = base16::decode_slice(hex_key, &mut key_bytes) + .map_err(|_| Error::StakesKeyDeserializationFailed)?; + debug_assert!(_bytes_written == key_bytes.len()); + let pub_key = AccountHash::new(key_bytes); + let balance = split_name + .next() + .and_then(|b| U512::from_dec_str(b).ok()) + .ok_or(Error::StakesDeserializationFailed)?; + stakes.insert(pub_key, balance); + } + if stakes.is_empty() { + return Err(Error::StakesNotFound); + } + Ok(Stakes(stakes)) + } + + /// Writes the current stakes to the contract's known urefs. + fn write(&mut self, stakes: &Stakes) { + // Encode the stakes as a set of uref names. + let mut new_urefs: BTreeSet = stakes.strings().collect(); + // Remove and add urefs to update the contract's known urefs accordingly. + for (name, _) in runtime::list_named_keys() { + if name.starts_with("v_") && !new_urefs.remove(&name) { + runtime::remove_key(&name); + } + } + for name in new_urefs { + runtime::put_key(&name, Key::Hash([0; 32])); + } + } +} + +impl ProofOfStake for ProofOfStakeContract {} + +pub fn bond() { + if !cfg!(feature = "enable-bonding") { + runtime::revert(ApiError::Unhandled) + } + + let validator = runtime::get_caller(); + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + // source purse + let source: URef = runtime::get_named_arg(ARG_PURSE); + + let mut pos_contract = ProofOfStakeContract; + pos_contract + .bond(validator, amount, source) + .unwrap_or_revert(); +} + +pub fn unbond() { + if !cfg!(feature = "enable-bonding") { + runtime::revert(ApiError::Unhandled) + } + + let validator = runtime::get_caller(); + let maybe_amount = runtime::get_named_arg(ARG_AMOUNT); + + let mut pos_contract = ProofOfStakeContract; + pos_contract + .unbond(validator, maybe_amount) + .unwrap_or_revert(); +} + +pub fn get_payment_purse() { + let pos_contract = ProofOfStakeContract; + let rights_controlled_purse = pos_contract.get_payment_purse().unwrap_or_revert(); + let return_value = CLValue::from_t(rights_controlled_purse).unwrap_or_revert(); + runtime::ret(return_value); +} + +pub fn set_refund_purse() { + let mut pos_contract = ProofOfStakeContract; + + let refund_purse: URef = runtime::get_named_arg(ARG_PURSE); + pos_contract + .set_refund_purse(refund_purse) + .unwrap_or_revert(); +} + +pub fn get_refund_purse() { + let pos_contract = ProofOfStakeContract; + // We purposely choose to remove the access rights so that we do not + // accidentally give rights for a purse to some contract that is not + // supposed to have it. + let maybe_refund_purse = pos_contract.get_refund_purse().unwrap_or_revert(); + let return_value = CLValue::from_t(maybe_refund_purse).unwrap_or_revert(); + runtime::ret(return_value); +} + +pub fn finalize_payment() { + let mut pos_contract = ProofOfStakeContract; + + let amount_spent: U512 = runtime::get_named_arg(ARG_AMOUNT); + let account: AccountHash = runtime::get_named_arg(ARG_ACCOUNT_KEY); + pos_contract + .finalize_payment(amount_spent, account) + .unwrap_or_revert(); +} diff --git a/smart-contracts/contracts/system/standard-payment-install/Cargo.toml b/smart-contracts/contracts/system/standard-payment-install/Cargo.toml new file mode 100644 index 0000000000..fc5e09fa80 --- /dev/null +++ b/smart-contracts/contracts/system/standard-payment-install/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "standard-payment-install" +version = "0.1.0" +authors = ["Fraser Hutchison "] +edition = "2018" + +[[bin]] +name = "standard_payment_install" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] +no-unstable-features = ["contract/no-unstable-features", "types/no-unstable-features"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +standard-payment = { version = "0.1.0", path = "../standard-payment" } +types = { version = "0.6.0", path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/system/standard-payment-install/src/main.rs b/smart-contracts/contracts/system/standard-payment-install/src/main.rs new file mode 100644 index 0000000000..3a894d66b4 --- /dev/null +++ b/smart-contracts/contracts/system/standard-payment-install/src/main.rs @@ -0,0 +1,58 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{boxed::Box, string::ToString, vec}; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use standard_payment::ARG_AMOUNT; +use types::{ + contracts::{EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, NamedKeys, Parameter}, + CLType, CLValue, +}; + +const METHOD_CALL: &str = "call"; +const HASH_KEY_NAME: &str = "standard_payment_hash"; +const ACCESS_KEY_NAME: &str = "standard_payment_access"; + +#[no_mangle] +pub extern "C" fn call() { + standard_payment::delegate(); +} + +#[no_mangle] +pub extern "C" fn install() { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + METHOD_CALL.to_string(), + vec![Parameter::new(ARG_AMOUNT, CLType::U512)], + CLType::Result { + ok: Box::new(CLType::Unit), + err: Box::new(CLType::U32), + }, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(entry_point); + + entry_points + }; + + let (contract_package_hash, access_uref) = storage::create_contract_package_at_hash(); + runtime::put_key(HASH_KEY_NAME, contract_package_hash.into()); + runtime::put_key(ACCESS_KEY_NAME, access_uref.into()); + + let named_keys = NamedKeys::new(); + + let (contract_key, _contract_version) = + storage::add_contract_version(contract_package_hash, entry_points, named_keys); + + let return_value = CLValue::from_t(contract_key).unwrap_or_revert(); + runtime::ret(return_value); +} diff --git a/smart-contracts/contracts/system/standard-payment/Cargo.toml b/smart-contracts/contracts/system/standard-payment/Cargo.toml new file mode 100644 index 0000000000..96e1f60428 --- /dev/null +++ b/smart-contracts/contracts/system/standard-payment/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "standard-payment" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[lib] +bench = false +doctest = false +test = false + +[[bin]] +name = "standard_payment" +path = "src/bin/main.rs" + +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/system/standard-payment/src/bin/main.rs b/smart-contracts/contracts/system/standard-payment/src/bin/main.rs new file mode 100644 index 0000000000..c710c807cf --- /dev/null +++ b/smart-contracts/contracts/system/standard-payment/src/bin/main.rs @@ -0,0 +1,7 @@ +#![no_std] +#![no_main] + +#[no_mangle] +pub extern "C" fn call() { + standard_payment::delegate(); +} diff --git a/smart-contracts/contracts/system/standard-payment/src/lib.rs b/smart-contracts/contracts/system/standard-payment/src/lib.rs new file mode 100644 index 0000000000..fe942d73bc --- /dev/null +++ b/smart-contracts/contracts/system/standard-payment/src/lib.rs @@ -0,0 +1,51 @@ +#![no_std] + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::standard_payment::{ + AccountProvider, MintProvider, ProofOfStakeProvider, StandardPayment, +}; +use types::{ApiError, RuntimeArgs, URef, U512}; + +const GET_PAYMENT_PURSE: &str = "get_payment_purse"; +pub const ARG_AMOUNT: &str = "amount"; + +struct StandardPaymentContract; + +impl AccountProvider for StandardPaymentContract { + fn get_main_purse(&self) -> Result { + Ok(account::get_main_purse()) + } +} + +impl MintProvider for StandardPaymentContract { + fn transfer_purse_to_purse( + &mut self, + source: URef, + target: URef, + amount: U512, + ) -> Result<(), ApiError> { + system::transfer_from_purse_to_purse(source, target, amount) + } +} + +impl ProofOfStakeProvider for StandardPaymentContract { + fn get_payment_purse(&mut self) -> Result { + let pos_pointer = system::get_proof_of_stake(); + let payment_purse = + runtime::call_contract(pos_pointer, GET_PAYMENT_PURSE, RuntimeArgs::default()); + Ok(payment_purse) + } +} + +impl StandardPayment for StandardPaymentContract {} + +pub fn delegate() { + let mut standard_payment_contract = StandardPaymentContract; + + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + + standard_payment_contract.pay(amount).unwrap_or_revert(); +} diff --git a/smart-contracts/contracts/system/test-mint-token/Cargo.toml b/smart-contracts/contracts/system/test-mint-token/Cargo.toml new file mode 100644 index 0000000000..811dec3e19 --- /dev/null +++ b/smart-contracts/contracts/system/test-mint-token/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "test-mint-token" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "test_mint_token" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/system/test-mint-token/src/main.rs b/smart-contracts/contracts/system/test-mint-token/src/main.rs new file mode 100644 index 0000000000..0c656d1002 --- /dev/null +++ b/smart-contracts/contracts/system/test-mint-token/src/main.rs @@ -0,0 +1,64 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use types::{runtime_args, ContractHash, RuntimeArgs, URef, U512}; + +use contract::contract_api::{runtime, system}; + +const ARG_AMOUNT: &str = "amount"; +const ARG_PURSE: &str = "purse"; +const ARG_SOURCE: &str = "source"; +const ARG_TARGET: &str = "target"; +const ARG_CREATE: &str = "create"; +const ARG_TRANSFER: &str = "transfer"; +const ARG_BALANCE: &str = "balance"; + +#[no_mangle] +pub extern "C" fn call() { + let mint: ContractHash = system::get_mint(); + + let source = get_purse(mint, 100); + let target = get_purse(mint, 300); + + assert!( + transfer(mint, source, target, U512::from(70)) == "Success!", + "transfer should succeed" + ); + + assert!( + balance(mint, source).unwrap() == U512::from(30), + "source purse balance incorrect" + ); + assert!( + balance(mint, target).unwrap() == U512::from(370), + "target balance incorrect" + ); +} + +fn get_purse(mint: ContractHash, amount: u64) -> URef { + let amount = U512::from(amount); + let args = runtime_args! { + ARG_AMOUNT => amount, + }; + runtime::call_contract::(mint, ARG_CREATE, args) +} + +fn transfer(mint: ContractHash, source: URef, target: URef, amount: U512) -> String { + let args = runtime_args! { + ARG_AMOUNT => amount, + ARG_SOURCE => source, + ARG_TARGET => target, + }; + runtime::call_contract::(mint, ARG_TRANSFER, args) +} + +fn balance(mint: ContractHash, purse: URef) -> Option { + let args = runtime_args! { + ARG_PURSE => purse, + }; + runtime::call_contract::>(mint, ARG_BALANCE, args) +} diff --git a/smart-contracts/contracts/test/add-gas-subcall/Cargo.toml b/smart-contracts/contracts/test/add-gas-subcall/Cargo.toml new file mode 100644 index 0000000000..a7d550452f --- /dev/null +++ b/smart-contracts/contracts/test/add-gas-subcall/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "add-gas-subcall" +version = "0.1.0" +authors = ["Fraser Hutchison "] +edition = "2018" + +[[bin]] +name = "add_gas_subcall" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/add-gas-subcall/src/main.rs b/smart-contracts/contracts/test/add-gas-subcall/src/main.rs new file mode 100644 index 0000000000..0f7dde5f8a --- /dev/null +++ b/smart-contracts/contracts/test/add-gas-subcall/src/main.rs @@ -0,0 +1,77 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate alloc; + +use alloc::string::String; + +use contract::contract_api::{runtime, storage}; + +use types::{ + runtime_args, ApiError, CLType, ContractHash, ContractVersion, EntryPoint, EntryPointAccess, + EntryPointType, EntryPoints, Parameter, RuntimeArgs, +}; + +// This is making use of the undocumented "FFI" function `gas()` which is used by the Wasm +// interpreter to charge gas for upcoming interpreted instructions. For further info on this, see +// https://docs.rs/pwasm-utils/0.12.0/pwasm_utils/fn.inject_gas_counter.html +mod unsafe_ffi { + extern "C" { + pub fn gas(amount: i32); + } +} + +fn safe_gas(amount: i32) { + unsafe { unsafe_ffi::gas(amount) } +} + +const SUBCALL_NAME: &str = "add_gas"; +const ADD_GAS_FROM_SESSION: &str = "add-gas-from-session"; +const ADD_GAS_VIA_SUBCALL: &str = "add-gas-via-subcall"; + +const ARG_GAS_AMOUNT: &str = "gas_amount"; +const ARG_METHOD_NAME: &str = "method_name"; + +#[no_mangle] +pub extern "C" fn add_gas() { + let amount: i32 = runtime::get_named_arg(ARG_GAS_AMOUNT); + safe_gas(amount); +} + +fn store() -> (ContractHash, ContractVersion) { + let entry_points = { + let mut entry_points = EntryPoints::new(); + let entry_point = EntryPoint::new( + SUBCALL_NAME, + vec![Parameter::new(ARG_GAS_AMOUNT, CLType::I32)], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + storage::new_contract(entry_points, None, None, None) +} + +#[no_mangle] +pub extern "C" fn call() { + let amount: i32 = runtime::get_named_arg(ARG_GAS_AMOUNT); + let method_name: String = runtime::get_named_arg(ARG_METHOD_NAME); + + match method_name.as_str() { + ADD_GAS_FROM_SESSION => safe_gas(amount), + ADD_GAS_VIA_SUBCALL => { + let (contract_hash, _contract_version) = store(); + runtime::call_contract( + contract_hash, + SUBCALL_NAME, + runtime_args! { ARG_GAS_AMOUNT => amount, }, + ) + } + _ => runtime::revert(ApiError::InvalidArgument), + } +} diff --git a/smart-contracts/contracts/test/add-update-associated-key/Cargo.toml b/smart-contracts/contracts/test/add-update-associated-key/Cargo.toml new file mode 100644 index 0000000000..c0318a9257 --- /dev/null +++ b/smart-contracts/contracts/test/add-update-associated-key/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "add-update-associated-key" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "authorized_keys" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/authorized-keys/src/main.rs b/smart-contracts/contracts/test/authorized-keys/src/main.rs new file mode 100644 index 0000000000..1209ee360e --- /dev/null +++ b/smart-contracts/contracts/test/authorized-keys/src/main.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{account, runtime}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + account::{AccountHash, ActionType, AddKeyFailure, Weight}, + ApiError, +}; + +const ARG_KEY_MANAGEMENT_THRESHOLD: &str = "key_management_threshold"; +const ARG_DEPLOY_THRESHOLD: &str = "deploy_threshold"; + +#[no_mangle] +pub extern "C" fn call() { + match account::add_associated_key(AccountHash::new([123; 32]), Weight::new(100)) { + Err(AddKeyFailure::DuplicateKey) => {} + Err(_) => runtime::revert(ApiError::User(50)), + Ok(_) => {} + }; + + let key_management_threshold: Weight = runtime::get_named_arg(ARG_KEY_MANAGEMENT_THRESHOLD); + let deploy_threshold: Weight = runtime::get_named_arg(ARG_DEPLOY_THRESHOLD); + + if key_management_threshold != Weight::new(0) { + account::set_action_threshold(ActionType::KeyManagement, key_management_threshold) + .unwrap_or_revert() + } + + if deploy_threshold != Weight::new(0) { + account::set_action_threshold(ActionType::Deployment, deploy_threshold).unwrap_or_revert() + } +} diff --git a/smart-contracts/contracts/test/contract-context/Cargo.toml b/smart-contracts/contracts/test/contract-context/Cargo.toml new file mode 100644 index 0000000000..4c3b2a1b67 --- /dev/null +++ b/smart-contracts/contracts/test/contract-context/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "contract-context" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "contract_context" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/contract-context/src/main.rs b/smart-contracts/contracts/test/contract-context/src/main.rs new file mode 100644 index 0000000000..d8f12dd764 --- /dev/null +++ b/smart-contracts/contracts/test/contract-context/src/main.rs @@ -0,0 +1,174 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{string::ToString, vec::Vec}; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + contracts::{ + EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, NamedKeys, + CONTRACT_INITIAL_VERSION, + }, + runtime_args, CLType, ContractHash, ContractPackageHash, ContractVersion, Key, RuntimeArgs, +}; + +const PACKAGE_HASH_KEY: &str = "package_hash_key"; +const PACKAGE_ACCESS_KEY: &str = "package_access_key"; +const CONTRACT_HASH_KEY: &str = "contract_hash_key"; +const CONTRACT_CODE: &str = "contract_code_test"; +const SESSION_CODE: &str = "session_code_test"; +const NEW_KEY: &str = "new_key"; +const NAMED_KEY: &str = "contract_named_key"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[no_mangle] +pub extern "C" fn session_code_test() { + assert!(runtime::get_key(PACKAGE_HASH_KEY).is_some()); + assert!(runtime::get_key(PACKAGE_ACCESS_KEY).is_some()); + assert!(runtime::get_key(NAMED_KEY).is_none()); +} + +#[no_mangle] +pub extern "C" fn contract_code_test() { + assert!(runtime::get_key(PACKAGE_HASH_KEY).is_none()); + assert!(runtime::get_key(PACKAGE_ACCESS_KEY).is_none()); + assert!(runtime::get_key(NAMED_KEY).is_some()); +} + +#[no_mangle] +pub extern "C" fn session_code_caller_as_session() { + let contract_package_hash = runtime::get_key(PACKAGE_HASH_KEY) + .expect("should have contract package key") + .into_hash() + .unwrap_or_revert(); + + runtime::call_versioned_contract::<()>( + contract_package_hash, + Some(CONTRACT_INITIAL_VERSION), + SESSION_CODE, + runtime_args! {}, + ); +} + +#[no_mangle] +pub extern "C" fn add_new_key() { + let uref = storage::new_uref(()); + runtime::put_key(NEW_KEY, uref.into()); +} + +#[no_mangle] +pub extern "C" fn add_new_key_as_session() { + let contract_package_hash = runtime::get_key(PACKAGE_HASH_KEY) + .expect("should have package hash") + .into_hash() + .unwrap_or_revert(); + + assert!(runtime::get_key(NEW_KEY).is_none()); + runtime::call_versioned_contract::<()>( + contract_package_hash, + Some(CONTRACT_INITIAL_VERSION), + "add_new_key", + runtime_args! {}, + ); + assert!(runtime::get_key(NEW_KEY).is_some()); +} + +#[no_mangle] +pub extern "C" fn session_code_caller_as_contract() { + let contract_package_key: Key = runtime::get_named_arg(PACKAGE_HASH_KEY); + let contract_package_hash = contract_package_key.into_hash().unwrap_or_revert(); + runtime::call_versioned_contract::<()>( + contract_package_hash, + Some(CONTRACT_INITIAL_VERSION), + SESSION_CODE, + runtime_args! {}, + ); +} + +fn create_entrypoints_1() -> EntryPoints { + let mut entry_points = EntryPoints::new(); + let session_code_test = EntryPoint::new( + SESSION_CODE.to_string(), + Vec::new(), + CLType::I32, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(session_code_test); + + let contract_code_test = EntryPoint::new( + CONTRACT_CODE.to_string(), + Vec::new(), + CLType::I32, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(contract_code_test); + + let session_code_caller_as_session = EntryPoint::new( + "session_code_caller_as_session".to_string(), + Vec::new(), + CLType::I32, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(session_code_caller_as_session); + + let session_code_caller_as_contract = EntryPoint::new( + "session_code_caller_as_contract".to_string(), + Vec::new(), + CLType::I32, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(session_code_caller_as_contract); + + let add_new_key = EntryPoint::new( + "add_new_key".to_string(), + Vec::new(), + CLType::I32, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(add_new_key); + let add_new_key_as_session = EntryPoint::new( + "add_new_key_as_session".to_string(), + Vec::new(), + CLType::I32, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(add_new_key_as_session); + + entry_points +} + +fn install_version_1(package_hash: ContractPackageHash) -> (ContractHash, ContractVersion) { + let contract_named_keys = { + let contract_variable = storage::new_uref(0); + + let mut named_keys = NamedKeys::new(); + named_keys.insert("contract_named_key".to_string(), contract_variable.into()); + named_keys + }; + + let entry_points = create_entrypoints_1(); + storage::add_contract_version(package_hash, entry_points, contract_named_keys) +} + +#[no_mangle] +pub extern "C" fn call() { + // Session contract + let (contract_package_hash, access_uref) = storage::create_contract_package_at_hash(); + + runtime::put_key(PACKAGE_HASH_KEY, contract_package_hash.into()); + runtime::put_key(PACKAGE_ACCESS_KEY, access_uref.into()); + let (contract_hash, contract_version) = install_version_1(contract_package_hash); + runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into()); + runtime::put_key(CONTRACT_HASH_KEY, Key::Hash(contract_hash)); +} diff --git a/smart-contracts/contracts/test/create-purse-01/Cargo.toml b/smart-contracts/contracts/test/create-purse-01/Cargo.toml new file mode 100644 index 0000000000..a470a779b7 --- /dev/null +++ b/smart-contracts/contracts/test/create-purse-01/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "create-purse-01" +version = "0.1.0" +authors = ["Henry Till ", "Ed Hastings "] +edition = "2018" + +[[bin]] +name = "deserialize_error" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/deserialize-error/src/main.rs b/smart-contracts/contracts/test/deserialize-error/src/main.rs new file mode 100644 index 0000000000..15a7ce0ee8 --- /dev/null +++ b/smart-contracts/contracts/test/deserialize-error/src/main.rs @@ -0,0 +1,106 @@ +#![no_std] +#![no_main] +#![allow(unused_imports)] + +extern crate alloc; + +// Can be removed once https://github.com/rust-lang/rustfmt/issues/3362 is resolved. +#[rustfmt::skip] +use alloc::vec; +use alloc::{collections::BTreeMap, vec::Vec}; + +use contract::{ + self, + contract_api::{self, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use contract_api::runtime; +use types::{ + api_error, + bytesrepr::{FromBytes, ToBytes}, + contracts::Parameters, + CLType, CLTyped, ContractHash, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Key, + RuntimeArgs, +}; + +#[no_mangle] +pub extern "C" fn do_nothing() { + // A function that does nothing. + // This is used to just pass the checks in `call_contract` on the host side. +} + +// Attacker copied to_ptr from `alloc_utils` as it was private +fn to_ptr(t: T) -> (*const u8, usize, Vec) { + let bytes = t.into_bytes().unwrap_or_revert(); + let ptr = bytes.as_ptr(); + let size = bytes.len(); + (ptr, size, bytes) +} + +mod malicious_ffi { + // Potential attacker has available every FFI for himself + extern "C" { + pub fn call_contract( + contract_hash_ptr: *const u8, + contract_hash_size: usize, + entry_point_name_ptr: *const u8, + entry_point_name_size: usize, + runtime_args_ptr: *const u8, + runtime_args_size: usize, + result_size: *mut usize, + ) -> i32; + } +} + +// This is half-baked runtime::call_contract with changed `extra_urefs` +// parameter with a desired payload that's supposed to bring the node down. +pub fn my_call_contract( + contract_hash: ContractHash, + _entry_point_name: &str, + runtime_args: RuntimeArgs, +) -> usize { + let (contract_hash_ptr, contract_hash_size, _bytes1) = to_ptr(contract_hash); + + let malicious_string = vec![255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + + let (runtime_args_ptr, runtime_args_size, _bytes2) = to_ptr(runtime_args); + + { + let mut bytes_written = 0usize; + let ret = unsafe { + malicious_ffi::call_contract( + contract_hash_ptr, + contract_hash_size, + malicious_string.as_ptr(), + malicious_string.len(), + runtime_args_ptr, + runtime_args_size, + &mut bytes_written as *mut usize, + ) + }; + api_error::result_from(ret).unwrap_or_revert(); + bytes_written + } +} + +#[no_mangle] +pub extern "C" fn call() { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + "do_nothing", + Parameters::default(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + let (contract_hash, _contract_version) = storage::new_contract(entry_points, None, None, None); + + my_call_contract(contract_hash, "do_nothing", RuntimeArgs::default()); +} diff --git a/smart-contracts/contracts/test/do-nothing-stored-caller/Cargo.toml b/smart-contracts/contracts/test/do-nothing-stored-caller/Cargo.toml new file mode 100644 index 0000000000..e21bfcb026 --- /dev/null +++ b/smart-contracts/contracts/test/do-nothing-stored-caller/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "do-nothing-stored-caller" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "do_nothing_stored_caller" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/do-nothing-stored-caller/src/main.rs b/smart-contracts/contracts/test/do-nothing-stored-caller/src/main.rs new file mode 100644 index 0000000000..8566b2d4be --- /dev/null +++ b/smart-contracts/contracts/test/do-nothing-stored-caller/src/main.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::contract_api::runtime; +use types::{contracts::ContractVersion, runtime_args, ContractPackageHash, RuntimeArgs}; + +const ENTRY_FUNCTION_NAME: &str = "delegate"; +const PURSE_NAME_ARG_NAME: &str = "purse_name"; +const ARG_CONTRACT_PACKAGE: &str = "contract_package"; +const ARG_NEW_PURSE_NAME: &str = "new_purse_name"; +const ARG_VERSION: &str = "version"; + +#[no_mangle] +pub extern "C" fn call() { + let contract_package_hash: ContractPackageHash = runtime::get_named_arg(ARG_CONTRACT_PACKAGE); + let new_purse_name: String = runtime::get_named_arg(ARG_NEW_PURSE_NAME); + let version_number: ContractVersion = runtime::get_named_arg(ARG_VERSION); + let contract_version = Some(version_number); + + let runtime_args = runtime_args! { + PURSE_NAME_ARG_NAME => new_purse_name, + }; + + runtime::call_versioned_contract( + contract_package_hash, + contract_version, + ENTRY_FUNCTION_NAME, + runtime_args, + ) +} diff --git a/smart-contracts/contracts/test/do-nothing-stored-upgrader/Cargo.toml b/smart-contracts/contracts/test/do-nothing-stored-upgrader/Cargo.toml new file mode 100644 index 0000000000..e570f95f98 --- /dev/null +++ b/smart-contracts/contracts/test/do-nothing-stored-upgrader/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "do-nothing-stored-upgrader" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "do_nothing_stored_upgrader" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +create-purse-01 = { path = "../create-purse-01" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/do-nothing-stored-upgrader/src/main.rs b/smart-contracts/contracts/test/do-nothing-stored-upgrader/src/main.rs new file mode 100644 index 0000000000..5cea656be2 --- /dev/null +++ b/smart-contracts/contracts/test/do-nothing-stored-upgrader/src/main.rs @@ -0,0 +1,61 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{string::ToString, vec::Vec}; +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use core::convert::TryInto; + +use types::{ + contracts::{EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, NamedKeys}, + CLType, Key, URef, +}; + +const ENTRY_FUNCTION_NAME: &str = "delegate"; +const DO_NOTHING_PACKAGE_HASH_KEY_NAME: &str = "do_nothing_package_hash"; +const DO_NOTHING_ACCESS_KEY_NAME: &str = "do_nothing_access"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[no_mangle] +pub extern "C" fn delegate() { + runtime::put_key("called_do_nothing_ver_2", Key::Hash([1u8; 32])); + create_purse_01::delegate() +} + +#[no_mangle] +pub extern "C" fn call() { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let delegate = EntryPoint::new( + ENTRY_FUNCTION_NAME.to_string(), + Vec::new(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(delegate); + + entry_points + }; + + let do_nothing_package_hash = + runtime::get_key(DO_NOTHING_PACKAGE_HASH_KEY_NAME).unwrap_or_revert(); + + let _do_nothing_uref: URef = runtime::get_key(DO_NOTHING_ACCESS_KEY_NAME) + .unwrap_or_revert() + .try_into() + .unwrap_or_revert(); + + let (contract_hash, contract_version) = storage::add_contract_version( + do_nothing_package_hash.into_hash().unwrap(), + entry_points, + NamedKeys::new(), + ); + runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into()); + runtime::put_key("end of upgrade", contract_hash.into()); +} diff --git a/smart-contracts/contracts/test/do-nothing-stored/Cargo.toml b/smart-contracts/contracts/test/do-nothing-stored/Cargo.toml new file mode 100644 index 0000000000..8b5e6052ac --- /dev/null +++ b/smart-contracts/contracts/test/do-nothing-stored/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "do-nothing-stored" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "do_nothing_stored" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/do-nothing-stored/src/main.rs b/smart-contracts/contracts/test/do-nothing-stored/src/main.rs new file mode 100644 index 0000000000..67708aece3 --- /dev/null +++ b/smart-contracts/contracts/test/do-nothing-stored/src/main.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{string::ToString, vec::Vec}; + +use contract::contract_api::{runtime, storage}; +use types::{ + contracts::{EntryPoint, EntryPoints}, + CLType, EntryPointAccess, EntryPointType, +}; + +const ENTRY_FUNCTION_NAME: &str = "delegate"; +const HASH_KEY_NAME: &str = "do_nothing_hash"; +const PACKAGE_HASH_KEY_NAME: &str = "do_nothing_package_hash"; +const ACCESS_KEY_NAME: &str = "do_nothing_access"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[no_mangle] +pub extern "C" fn delegate() {} + +#[no_mangle] +pub extern "C" fn call() { + let entry_points = { + let mut entry_points = EntryPoints::new(); + let entry_point = EntryPoint::new( + ENTRY_FUNCTION_NAME.to_string(), + Vec::new(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + entry_points + }; + + let (contract_hash, contract_version) = storage::new_contract( + entry_points, + None, + Some(PACKAGE_HASH_KEY_NAME.to_string()), + Some(ACCESS_KEY_NAME.to_string()), + ); + + runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into()); + runtime::put_key(HASH_KEY_NAME, contract_hash.into()); +} diff --git a/smart-contracts/contracts/test/do-nothing/Cargo.toml b/smart-contracts/contracts/test/do-nothing/Cargo.toml new file mode 100644 index 0000000000..7a8faf526f --- /dev/null +++ b/smart-contracts/contracts/test/do-nothing/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "do-nothing" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "do_nothing" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } diff --git a/smart-contracts/contracts/test/do-nothing/src/main.rs b/smart-contracts/contracts/test/do-nothing/src/main.rs new file mode 100644 index 0000000000..8c5695aff9 --- /dev/null +++ b/smart-contracts/contracts/test/do-nothing/src/main.rs @@ -0,0 +1,11 @@ +#![no_std] +#![no_main] + +// Required to bring `#[panic_handler]` from `contract::handlers` into scope. +#[allow(unused_imports, clippy::single_component_path_imports)] +use contract; + +#[no_mangle] +pub extern "C" fn call() { + // This body intentionally left empty. +} diff --git a/smart-contracts/contracts/test/ee-221-regression/Cargo.toml b/smart-contracts/contracts/test/ee-221-regression/Cargo.toml new file mode 100644 index 0000000000..5e9d6c9ec6 --- /dev/null +++ b/smart-contracts/contracts/test/ee-221-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-221-regression" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "ee_221_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-221-regression/src/main.rs b/smart-contracts/contracts/test/ee-221-regression/src/main.rs new file mode 100644 index 0000000000..4008b8a832 --- /dev/null +++ b/smart-contracts/contracts/test/ee-221-regression/src/main.rs @@ -0,0 +1,18 @@ +#![no_std] +#![no_main] + +use contract::contract_api::{runtime, storage}; +use types::Key; + +#[no_mangle] +pub extern "C" fn call() { + let res1 = runtime::get_key("nonexistinguref"); + assert!(res1.is_none()); + + let key = Key::URef(storage::new_uref(())); + runtime::put_key("nonexistinguref", key); + + let res2 = runtime::get_key("nonexistinguref"); + + assert_eq!(res2, Some(key)); +} diff --git a/smart-contracts/contracts/test/ee-401-regression-call/Cargo.toml b/smart-contracts/contracts/test/ee-401-regression-call/Cargo.toml new file mode 100644 index 0000000000..88f6b9f42e --- /dev/null +++ b/smart-contracts/contracts/test/ee-401-regression-call/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-401-regression-call" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[[bin]] +name = "ee_401_regression_call" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-401-regression-call/src/main.rs b/smart-contracts/contracts/test/ee-401-regression-call/src/main.rs new file mode 100644 index 0000000000..94a04765ff --- /dev/null +++ b/smart-contracts/contracts/test/ee-401-regression-call/src/main.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::ToString; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ApiError, ContractHash, RuntimeArgs, URef}; + +#[no_mangle] +pub extern "C" fn call() { + let contract_hash: ContractHash = runtime::get_key("hello_ext") + .unwrap_or_revert_with(ApiError::GetKey) + .into_hash() + .unwrap_or_revert(); + + let result: URef = runtime::call_contract(contract_hash, "hello_ext", RuntimeArgs::default()); + + let value = storage::read(result); + + assert_eq!(Ok(Some("Hello, world!".to_string())), value); +} diff --git a/smart-contracts/contracts/test/ee-401-regression/Cargo.toml b/smart-contracts/contracts/test/ee-401-regression/Cargo.toml new file mode 100644 index 0000000000..c0c15fe21a --- /dev/null +++ b/smart-contracts/contracts/test/ee-401-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-401-regression" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[[bin]] +name = "ee_401_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-401-regression/src/main.rs b/smart-contracts/contracts/test/ee-401-regression/src/main.rs new file mode 100644 index 0000000000..5e858d8635 --- /dev/null +++ b/smart-contracts/contracts/test/ee-401-regression/src/main.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] +#![allow(unused_imports)] + +#[macro_use] +extern crate alloc; + +use alloc::{collections::BTreeMap, string::String}; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + contracts::Parameters, CLType, CLValue, EntryPoint, EntryPointAccess, EntryPointType, + EntryPoints, Parameter, URef, +}; + +const HELLO_EXT: &str = "hello_ext"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[no_mangle] +pub extern "C" fn hello_ext() { + let test_string = String::from("Hello, world!"); + let test_uref: URef = storage::new_uref(test_string); + let return_value = CLValue::from_t(test_uref).unwrap_or_revert(); + runtime::ret(return_value) +} + +#[no_mangle] +pub extern "C" fn call() { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + HELLO_EXT, + Parameters::new(), + CLType::URef, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + let (contract_hash, contract_version) = storage::new_contract(entry_points, None, None, None); + + runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into()); + runtime::put_key(HELLO_EXT, contract_hash.into()); +} diff --git a/smart-contracts/contracts/test/ee-441-rng-state/Cargo.toml b/smart-contracts/contracts/test/ee-441-rng-state/Cargo.toml new file mode 100644 index 0000000000..4f02277e06 --- /dev/null +++ b/smart-contracts/contracts/test/ee-441-rng-state/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-441-rng-state" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[[bin]] +name = "ee_441_rng_state" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-441-rng-state/src/main.rs b/smart-contracts/contracts/test/ee-441-rng-state/src/main.rs new file mode 100644 index 0000000000..0acf1be9d7 --- /dev/null +++ b/smart-contracts/contracts/test/ee-441-rng-state/src/main.rs @@ -0,0 +1,90 @@ +#![no_std] +#![no_main] +#![allow(unused_imports)] + +extern crate alloc; + +use alloc::{collections::BTreeMap, string::String}; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + contracts::Parameters, ApiError, CLType, CLValue, EntryPoint, EntryPointAccess, EntryPointType, + EntryPoints, Key, RuntimeArgs, URef, U512, +}; + +const ARG_FLAG: &str = "flag"; + +#[no_mangle] +pub extern "C" fn do_nothing() { + // Doesn't advance RNG of the runtime + runtime::ret(CLValue::from_t("Hello, world!").unwrap_or_revert()) +} + +#[no_mangle] +pub extern "C" fn do_something() { + // Advances RNG of the runtime + let test_string = String::from("Hello, world!"); + + let test_uref: URef = storage::new_uref(test_string); + let return_value = CLValue::from_t(test_uref).unwrap_or_revert(); + runtime::ret(return_value) +} + +#[no_mangle] +pub extern "C" fn call() { + let flag: String = runtime::get_named_arg(ARG_FLAG); + + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let do_nothing_entry_point = EntryPoint::new( + "do_nothing", + Parameters::default(), + CLType::String, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(do_nothing_entry_point); + + let do_something_entry_point = EntryPoint::new( + "do_something", + Parameters::default(), + CLType::URef, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(do_something_entry_point); + + entry_points + }; + let (contract_hash, _contract_version) = storage::new_contract(entry_points, None, None, None); + + if flag == "pass1" { + // Two calls should forward the internal RNG. This pass is a baseline. + let uref1: URef = storage::new_uref(U512::from(0)); + let uref2: URef = storage::new_uref(U512::from(1)); + runtime::put_key("uref1", Key::URef(uref1)); + runtime::put_key("uref2", Key::URef(uref2)); + } else if flag == "pass2" { + let uref1: URef = storage::new_uref(U512::from(0)); + runtime::put_key("uref1", Key::URef(uref1)); + // do_nothing doesn't do anything. It SHOULD not forward the internal RNG. + let result: String = + runtime::call_contract(contract_hash, "do_nothing", RuntimeArgs::default()); + assert_eq!(result, "Hello, world!"); + let uref2: URef = storage::new_uref(U512::from(1)); + runtime::put_key("uref2", Key::URef(uref2)); + } else if flag == "pass3" { + let uref1: URef = storage::new_uref(U512::from(0)); + runtime::put_key("uref1", Key::URef(uref1)); + // do_something returns a new uref, and it should forward the internal RNG. + let uref2: URef = + runtime::call_contract(contract_hash, "do_something", RuntimeArgs::default()); + runtime::put_key("uref2", Key::URef(uref2)); + } +} diff --git a/smart-contracts/contracts/test/ee-460-regression/Cargo.toml b/smart-contracts/contracts/test/ee-460-regression/Cargo.toml new file mode 100644 index 0000000000..93394a4fa3 --- /dev/null +++ b/smart-contracts/contracts/test/ee-460-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-460-regression" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "ee_460_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-460-regression/src/main.rs b/smart-contracts/contracts/test/ee-460-regression/src/main.rs new file mode 100644 index 0000000000..8feb2e605a --- /dev/null +++ b/smart-contracts/contracts/test/ee-460-regression/src/main.rs @@ -0,0 +1,15 @@ +#![no_std] +#![no_main] + +use contract::contract_api::{runtime, system}; +use types::{account::AccountHash, ApiError, U512}; + +const ARG_AMOUNT: &str = "amount"; + +#[no_mangle] +pub extern "C" fn call() { + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + let account_hash = AccountHash::new([42; 32]); + let result = system::transfer_to_account(account_hash, amount); + assert_eq!(result, Err(ApiError::Transfer)) +} diff --git a/smart-contracts/contracts/test/ee-532-regression/Cargo.toml b/smart-contracts/contracts/test/ee-532-regression/Cargo.toml new file mode 100644 index 0000000000..80b7164edd --- /dev/null +++ b/smart-contracts/contracts/test/ee-532-regression/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ee-532-regression" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "ee_532_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } diff --git a/smart-contracts/contracts/test/ee-532-regression/src/main.rs b/smart-contracts/contracts/test/ee-532-regression/src/main.rs new file mode 100644 index 0000000000..c7dcdd946c --- /dev/null +++ b/smart-contracts/contracts/test/ee-532-regression/src/main.rs @@ -0,0 +1,11 @@ +#![no_std] +#![no_main] + +// Required to bring `#[panic_handler]` from `contract::handlers` into scope. +#[allow(unused_imports, clippy::single_component_path_imports)] +use contract; + +#[no_mangle] +pub extern "C" fn call() { + // Does nothing +} diff --git a/smart-contracts/contracts/test/ee-536-regression/Cargo.toml b/smart-contracts/contracts/test/ee-536-regression/Cargo.toml new file mode 100644 index 0000000000..cc52e18bdf --- /dev/null +++ b/smart-contracts/contracts/test/ee-536-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-536-regression" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "ee_536_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-536-regression/src/main.rs b/smart-contracts/contracts/test/ee-536-regression/src/main.rs new file mode 100644 index 0000000000..e36afc1c4f --- /dev/null +++ b/smart-contracts/contracts/test/ee-536-regression/src/main.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{account, runtime}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + account::{ + AccountHash, ActionType, RemoveKeyFailure, SetThresholdFailure, UpdateKeyFailure, Weight, + }, + ApiError, +}; + +#[no_mangle] +pub extern "C" fn call() { + // Starts with deployment=1, key_management=1 + let key_1 = AccountHash::new([42; 32]); + let key_2 = AccountHash::new([43; 32]); + + // Total keys weight = 11 (identity + new key's weight) + account::add_associated_key(key_1, Weight::new(10)).unwrap_or_revert(); + account::add_associated_key(key_2, Weight::new(11)).unwrap_or_revert(); + + account::set_action_threshold(ActionType::KeyManagement, Weight::new(13)).unwrap_or_revert(); + account::set_action_threshold(ActionType::Deployment, Weight::new(10)).unwrap_or_revert(); + + match account::remove_associated_key(key_2) { + Err(RemoveKeyFailure::ThresholdViolation) => { + // Shouldn't be able to remove key because key threshold == 11 and + // removing would violate the constraint + } + Err(_) => runtime::revert(ApiError::User(300)), + Ok(_) => runtime::revert(ApiError::User(301)), + } + + match account::set_action_threshold(ActionType::KeyManagement, Weight::new(255)) { + Err(SetThresholdFailure::InsufficientTotalWeight) => { + // Changing key management threshold to this value would lock down + // account for future operations + } + Err(_) => runtime::revert(ApiError::User(400)), + Ok(_) => runtime::revert(ApiError::User(401)), + } + // Key management threshold is 11, so changing threshold of key from 10 to 11 + // would violate + match account::update_associated_key(key_2, Weight::new(1)) { + Err(UpdateKeyFailure::ThresholdViolation) => { + // Changing it would mean the total weight would be identity(1) + + // key_1(10) + key_2(1) < key_mgmt(13) + } + Err(_) => runtime::revert(ApiError::User(500)), + Ok(_) => runtime::revert(ApiError::User(501)), + } +} diff --git a/smart-contracts/contracts/test/ee-539-regression/Cargo.toml b/smart-contracts/contracts/test/ee-539-regression/Cargo.toml new file mode 100644 index 0000000000..a518b017d6 --- /dev/null +++ b/smart-contracts/contracts/test/ee-539-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-539-regression" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "ee_539_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-539-regression/src/main.rs b/smart-contracts/contracts/test/ee-539-regression/src/main.rs new file mode 100644 index 0000000000..c9aac645d3 --- /dev/null +++ b/smart-contracts/contracts/test/ee-539-regression/src/main.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{account, runtime}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::account::{AccountHash, ActionType, Weight}; + +const ARG_KEY_MANAGEMENT_THRESHOLD: &str = "key_management_threshold"; +const ARG_DEPLOYMENT_THRESHOLD: &str = "deployment_threshold"; + +#[no_mangle] +pub extern "C" fn call() { + account::add_associated_key(AccountHash::new([123; 32]), Weight::new(254)).unwrap_or_revert(); + let key_management_threshold: Weight = runtime::get_named_arg(ARG_KEY_MANAGEMENT_THRESHOLD); + let deployment_threshold: Weight = runtime::get_named_arg(ARG_DEPLOYMENT_THRESHOLD); + + account::set_action_threshold(ActionType::KeyManagement, key_management_threshold) + .unwrap_or_revert(); + account::set_action_threshold(ActionType::Deployment, deployment_threshold).unwrap_or_revert(); +} diff --git a/smart-contracts/contracts/test/ee-549-regression/Cargo.toml b/smart-contracts/contracts/test/ee-549-regression/Cargo.toml new file mode 100644 index 0000000000..9047ab8acb --- /dev/null +++ b/smart-contracts/contracts/test/ee-549-regression/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "ee-549-regression" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "ee_549_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } + diff --git a/smart-contracts/contracts/test/ee-549-regression/src/main.rs b/smart-contracts/contracts/test/ee-549-regression/src/main.rs new file mode 100644 index 0000000000..1a301f1f36 --- /dev/null +++ b/smart-contracts/contracts/test/ee-549-regression/src/main.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +use contract::contract_api::{runtime, system}; +use types::{runtime_args, RuntimeArgs}; + +const SET_REFUND_PURSE: &str = "set_refund_purse"; +const ARG_PURSE: &str = "purse"; + +fn malicious_revenue_stealing_contract() { + let contract_hash = system::get_proof_of_stake(); + + let args = runtime_args! { + ARG_PURSE => system::create_purse(), + }; + + runtime::call_contract::<()>(contract_hash, SET_REFUND_PURSE, args); +} + +#[no_mangle] +pub extern "C" fn call() { + malicious_revenue_stealing_contract() +} diff --git a/smart-contracts/contracts/test/ee-550-regression/Cargo.toml b/smart-contracts/contracts/test/ee-550-regression/Cargo.toml new file mode 100644 index 0000000000..fd8192b504 --- /dev/null +++ b/smart-contracts/contracts/test/ee-550-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-550-regression" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "ee_550_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-550-regression/src/main.rs b/smart-contracts/contracts/test/ee-550-regression/src/main.rs new file mode 100644 index 0000000000..818e4cf10e --- /dev/null +++ b/smart-contracts/contracts/test/ee-550-regression/src/main.rs @@ -0,0 +1,75 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::{ + contract_api::{account, runtime}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + account::{AccountHash, ActionType, Weight}, + ApiError, +}; + +#[repr(u16)] +enum Error { + AddKey1 = 0, + AddKey2 = 1, + SetActionThreshold = 2, + RemoveKey = 3, + UpdateKey = 4, + UnknownPass = 5, +} + +impl Into for Error { + fn into(self) -> ApiError { + ApiError::User(self as u16) + } +} + +const KEY_1_ADDR: [u8; 32] = [100; 32]; +const KEY_2_ADDR: [u8; 32] = [101; 32]; + +const ARG_PASS: &str = "pass"; + +#[no_mangle] +pub extern "C" fn call() { + let pass: String = runtime::get_named_arg(ARG_PASS); + match pass.as_str() { + "init_remove" => { + account::add_associated_key(AccountHash::new(KEY_1_ADDR), Weight::new(2)) + .unwrap_or_revert_with(Error::AddKey1); + account::add_associated_key(AccountHash::new(KEY_2_ADDR), Weight::new(255)) + .unwrap_or_revert_with(Error::AddKey2); + account::set_action_threshold(ActionType::KeyManagement, Weight::new(254)) + .unwrap_or_revert_with(Error::SetActionThreshold); + } + "test_remove" => { + // Deployed with two keys of weights 2 and 255 (total saturates at 255) to satisfy new + // threshold + account::remove_associated_key(AccountHash::new(KEY_1_ADDR)) + .unwrap_or_revert_with(Error::RemoveKey); + } + + "init_update" => { + account::add_associated_key(AccountHash::new(KEY_1_ADDR), Weight::new(3)) + .unwrap_or_revert_with(Error::AddKey1); + account::add_associated_key(AccountHash::new(KEY_2_ADDR), Weight::new(255)) + .unwrap_or_revert_with(Error::AddKey2); + account::set_action_threshold(ActionType::KeyManagement, Weight::new(254)) + .unwrap_or_revert_with(Error::SetActionThreshold); + } + "test_update" => { + // Deployed with two keys of weights 3 and 255 (total saturates at 255) to satisfy new + // threshold + account::update_associated_key(AccountHash::new(KEY_1_ADDR), Weight::new(1)) + .unwrap_or_revert_with(Error::UpdateKey); + } + _ => { + runtime::revert(Error::UnknownPass); + } + } +} diff --git a/smart-contracts/contracts/test/ee-572-regression-create/Cargo.toml b/smart-contracts/contracts/test/ee-572-regression-create/Cargo.toml new file mode 100644 index 0000000000..4e713cad25 --- /dev/null +++ b/smart-contracts/contracts/test/ee-572-regression-create/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-572-regression-create" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[[bin]] +name = "ee_572_regression_create" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-572-regression-create/src/main.rs b/smart-contracts/contracts/test/ee-572-regression-create/src/main.rs new file mode 100644 index 0000000000..aa2bc3b4d4 --- /dev/null +++ b/smart-contracts/contracts/test/ee-572-regression-create/src/main.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use core::convert::Into; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + contracts::Parameters, AccessRights, CLType, CLValue, EntryPoint, EntryPointAccess, + EntryPointType, EntryPoints, URef, +}; + +const DATA: &str = "data"; +const CONTRACT_NAME: &str = "create"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[no_mangle] +pub extern "C" fn create() { + let reference: URef = storage::new_uref(DATA); + let read_only_reference: URef = URef::new(reference.addr(), AccessRights::READ); + let return_value = CLValue::from_t(read_only_reference).unwrap_or_revert(); + runtime::ret(return_value) +} + +#[no_mangle] +pub extern "C" fn call() { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + "create", + Parameters::default(), + CLType::URef, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + let (contract_hash, contract_version) = storage::new_contract(entry_points, None, None, None); + runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into()); + runtime::put_key(CONTRACT_NAME, contract_hash.into()); +} diff --git a/smart-contracts/contracts/test/ee-572-regression-escalate/Cargo.toml b/smart-contracts/contracts/test/ee-572-regression-escalate/Cargo.toml new file mode 100644 index 0000000000..15c2edc80e --- /dev/null +++ b/smart-contracts/contracts/test/ee-572-regression-escalate/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-572-regression-escalate" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[[bin]] +name = "ee_572_regression_escalate" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-572-regression-escalate/src/main.rs b/smart-contracts/contracts/test/ee-572-regression-escalate/src/main.rs new file mode 100644 index 0000000000..1ff44965f6 --- /dev/null +++ b/smart-contracts/contracts/test/ee-572-regression-escalate/src/main.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +use contract::contract_api::{runtime, storage}; +use types::{AccessRights, ContractHash, RuntimeArgs, URef}; + +const REPLACEMENT_DATA: &str = "bawitdaba"; +const ARG_CONTRACT_HASH: &str = "contract_hash"; + +#[no_mangle] +pub extern "C" fn call() { + let contract_hash: ContractHash = runtime::get_named_arg(ARG_CONTRACT_HASH); + + let reference: URef = runtime::call_contract(contract_hash, "create", RuntimeArgs::default()); + let forged_reference: URef = URef::new(reference.addr(), AccessRights::READ_ADD_WRITE); + storage::write(forged_reference, REPLACEMENT_DATA) +} diff --git a/smart-contracts/contracts/test/ee-584-regression/Cargo.toml b/smart-contracts/contracts/test/ee-584-regression/Cargo.toml new file mode 100644 index 0000000000..927ee0ed93 --- /dev/null +++ b/smart-contracts/contracts/test/ee-584-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-584-regression" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "ee_584_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-584-regression/src/main.rs b/smart-contracts/contracts/test/ee-584-regression/src/main.rs new file mode 100644 index 0000000000..969d9dcc95 --- /dev/null +++ b/smart-contracts/contracts/test/ee-584-regression/src/main.rs @@ -0,0 +1,15 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::contract_api::{runtime, storage}; +use types::ApiError; + +#[no_mangle] +pub extern "C" fn call() { + let _ = storage::new_uref(String::from("Hello, World!")); + runtime::revert(ApiError::User(999)) +} diff --git a/smart-contracts/contracts/test/ee-597-regression/Cargo.toml b/smart-contracts/contracts/test/ee-597-regression/Cargo.toml new file mode 100644 index 0000000000..27f5959e69 --- /dev/null +++ b/smart-contracts/contracts/test/ee-597-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-597-regression" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "ee_597_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-597-regression/src/main.rs b/smart-contracts/contracts/test/ee-597-regression/src/main.rs new file mode 100644 index 0000000000..976719e23b --- /dev/null +++ b/smart-contracts/contracts/test/ee-597-regression/src/main.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use contract::contract_api::{account, runtime, system}; +use types::{runtime_args, ContractHash, RuntimeArgs, URef, U512}; + +const ARG_AMOUNT: &str = "amount"; +const ARG_PURSE: &str = "purse"; +const BOND: &str = "bond"; + +fn bond(contract_hash: ContractHash, bond_amount: U512, bonding_purse: URef) { + let runtime_args = runtime_args! { + ARG_AMOUNT => bond_amount, + ARG_PURSE => bonding_purse, + }; + runtime::call_contract::<()>(contract_hash, BOND, runtime_args); +} + +#[no_mangle] +pub extern "C" fn call() { + bond( + system::get_proof_of_stake(), + U512::from(0), + account::get_main_purse(), + ); +} diff --git a/smart-contracts/contracts/test/ee-598-regression/Cargo.toml b/smart-contracts/contracts/test/ee-598-regression/Cargo.toml new file mode 100644 index 0000000000..1960d8f4c8 --- /dev/null +++ b/smart-contracts/contracts/test/ee-598-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-598-regression" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "ee_598_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-598-regression/src/main.rs b/smart-contracts/contracts/test/ee-598-regression/src/main.rs new file mode 100644 index 0000000000..60e481d6de --- /dev/null +++ b/smart-contracts/contracts/test/ee-598-regression/src/main.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +use contract::contract_api::{account, runtime, system}; +use types::{runtime_args, ContractHash, RuntimeArgs, URef, U512}; + +const ARG_AMOUNT: &str = "amount"; +const ARG_PURSE: &str = "purse"; +const BOND: &str = "bond"; +const UNBOND: &str = "unbond"; + +fn bond(contract_hash: ContractHash, bond_amount: U512, bonding_purse: URef) { + let runtime_args = runtime_args! { + ARG_AMOUNT => bond_amount, + ARG_PURSE => bonding_purse, + }; + runtime::call_contract::<()>(contract_hash, BOND, runtime_args); +} + +fn unbond(contract_hash: ContractHash, unbond_amount: Option) { + let args = runtime_args! { + ARG_AMOUNT => unbond_amount, + }; + runtime::call_contract::<()>(contract_hash, UNBOND, args); +} + +#[no_mangle] +pub extern "C" fn call() { + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + + let contract_hash = system::get_proof_of_stake(); + bond(contract_hash, amount, account::get_main_purse()); + unbond(contract_hash, Some(amount + 1)); +} diff --git a/smart-contracts/contracts/test/ee-599-regression/Cargo.toml b/smart-contracts/contracts/test/ee-599-regression/Cargo.toml new file mode 100644 index 0000000000..7aa1a4e0c9 --- /dev/null +++ b/smart-contracts/contracts/test/ee-599-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-599-regression" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "ee_599_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-599-regression/src/main.rs b/smart-contracts/contracts/test/ee-599-regression/src/main.rs new file mode 100644 index 0000000000..34b13e6f2f --- /dev/null +++ b/smart-contracts/contracts/test/ee-599-regression/src/main.rs @@ -0,0 +1,198 @@ +#![no_std] +#![no_main] +#![allow(dead_code)] +#![allow(unused_imports)] + +extern crate alloc; + +use alloc::{collections::BTreeMap, string::String}; + +use contract::{ + contract_api::{account, runtime, storage, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + account::AccountHash, + contracts::{NamedKeys, Parameters}, + ApiError, CLType, ContractHash, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Key, + RuntimeArgs, URef, U512, +}; + +const DONATION_AMOUNT: u64 = 1; +// Different name just to make sure any routine that deals with named keys coming from different +// sources wouldn't overlap (if ever that's possible) +const DONATION_PURSE_COPY: &str = "donation_purse_copy"; +const DONATION_PURSE: &str = "donation_purse"; +const GET_MAIN_PURSE: &str = "get_main_purse_ext"; +const MAINTAINER: &str = "maintainer"; +const METHOD_CALL: &str = "call"; +const METHOD_INSTALL: &str = "install"; +const TRANSFER_FROM_PURSE_TO_ACCOUNT: &str = "transfer_from_purse_to_account_ext"; +const TRANSFER_FROM_PURSE_TO_PURSE: &str = "transfer_from_purse_to_purse_ext"; +const TRANSFER_FUNDS_EXT: &str = "transfer_funds_ext"; +const TRANSFER_FUNDS_KEY: &str = "transfer_funds"; +const TRANSFER_TO_ACCOUNT: &str = "transfer_to_account_ext"; + +const ARG_METHOD: &str = "method"; +const ARG_CONTRACTKEY: &str = "contract_key"; +const ARG_SUBCONTRACTMETHODFWD: &str = "sub_contract_method_fwd"; + +enum TransferFunds { + Method = 0, +} + +#[repr(u16)] +enum ContractError { + InvalidDelegateMethod = 0, +} + +impl Into for ContractError { + fn into(self) -> ApiError { + ApiError::User(self as u16) + } +} + +fn get_maintainer_account_hash() -> Result { + // Obtain maintainer address from the contract's named keys + let maintainer_key = runtime::get_key(MAINTAINER).ok_or(ApiError::GetKey)?; + maintainer_key + .into_account() + .ok_or(ApiError::UnexpectedKeyVariant) +} + +fn get_donation_purse() -> Result { + let donation_key = runtime::get_key(DONATION_PURSE).ok_or(ApiError::GetKey)?; + donation_key + .into_uref() + .ok_or(ApiError::UnexpectedKeyVariant) +} + +#[no_mangle] +pub extern "C" fn transfer_from_purse_to_purse_ext() { + // Donation box is the purse funds will be transferred into + let donation_purse = get_donation_purse().unwrap_or_revert(); + + let main_purse = account::get_main_purse(); + + system::transfer_from_purse_to_purse(main_purse, donation_purse, U512::from(DONATION_AMOUNT)) + .unwrap_or_revert(); +} + +#[no_mangle] +pub extern "C" fn get_main_purse_ext() {} + +#[no_mangle] +pub extern "C" fn transfer_from_purse_to_account_ext() { + let main_purse = account::get_main_purse(); + // This is the address of account which installed the contract + let maintainer_account_hash = get_maintainer_account_hash().unwrap_or_revert(); + system::transfer_from_purse_to_account( + main_purse, + maintainer_account_hash, + U512::from(DONATION_AMOUNT), + ) + .unwrap_or_revert(); +} + +#[no_mangle] +pub extern "C" fn transfer_to_account_ext() { + // This is the address of account which installed the contract + let maintainer_account_hash = get_maintainer_account_hash().unwrap_or_revert(); + system::transfer_to_account(maintainer_account_hash, U512::from(DONATION_AMOUNT)) + .unwrap_or_revert(); + let _main_purse = account::get_main_purse(); +} + +/// Registers a function and saves it in callers named keys +fn delegate() -> Result<(), ApiError> { + let method: String = runtime::get_named_arg(ARG_METHOD); + match method.as_str() { + METHOD_INSTALL => { + // Create a purse that should be known to the contract regardless of the + // calling context still owned by the account that deploys the contract + let purse = system::create_purse(); + let maintainer = runtime::get_caller(); + // Keys below will make it possible to use within the called contract + let known_keys: NamedKeys = { + let mut keys = NamedKeys::new(); + // "donation_purse" is the purse owner of the contract can transfer funds from + // callers + keys.insert(DONATION_PURSE.into(), purse.into()); + // "maintainer" is the person who installed this contract + keys.insert(MAINTAINER.into(), Key::Account(maintainer)); + keys + }; + // Install the contract with associated owner-related keys + // let contract_ref = storage::store_function_at_hash(TRANSFER_FUNDS_EXT, known_keys); + + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point_1 = EntryPoint::new( + TRANSFER_FROM_PURSE_TO_ACCOUNT, + Parameters::default(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point_1); + + let entry_point_2 = EntryPoint::new( + TRANSFER_TO_ACCOUNT, + Parameters::default(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point_2); + + let entry_point_3 = EntryPoint::new( + TRANSFER_TO_ACCOUNT, + Parameters::default(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point_3); + + let entry_point_4 = EntryPoint::new( + TRANSFER_FROM_PURSE_TO_PURSE, + Parameters::default(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point_4); + + entry_points + }; + + let (contract_hash, _contract_version) = + storage::new_contract(entry_points, Some(known_keys), None, None); + runtime::put_key(TRANSFER_FUNDS_KEY, contract_hash.into()); + // For easy access in outside world here `donation` purse is also attached + // to the account + runtime::put_key(DONATION_PURSE_COPY, purse.into()); + } + METHOD_CALL => { + // This comes from outside i.e. after deploying the contract, this key is queried, + // and then passed into the call + let contract_key: ContractHash = runtime::get_named_arg(ARG_CONTRACTKEY); + + // This is a method that's gets forwarded into the sub contract + let subcontract_method: String = runtime::get_named_arg(ARG_SUBCONTRACTMETHODFWD); + runtime::call_contract::<()>(contract_key, &subcontract_method, RuntimeArgs::default()); + } + _ => return Err(ContractError::InvalidDelegateMethod.into()), + } + Ok(()) +} + +#[no_mangle] +pub extern "C" fn call() { + delegate().unwrap_or_revert(); +} diff --git a/smart-contracts/contracts/test/ee-601-regression/Cargo.toml b/smart-contracts/contracts/test/ee-601-regression/Cargo.toml new file mode 100644 index 0000000000..0faebee289 --- /dev/null +++ b/smart-contracts/contracts/test/ee-601-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-601-regression" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "ee_601_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-601-regression/src/main.rs b/smart-contracts/contracts/test/ee-601-regression/src/main.rs new file mode 100644 index 0000000000..2a9672202e --- /dev/null +++ b/smart-contracts/contracts/test/ee-601-regression/src/main.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::{String, ToString}; + +use contract::{ + contract_api::{account, runtime, storage, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ApiError, Phase, RuntimeArgs, URef, U512}; + +const GET_PAYMENT_PURSE: &str = "get_payment_purse"; +const NEW_UREF_RESULT_UREF_NAME: &str = "new_uref_result"; +const ARG_AMOUNT: &str = "amount"; + +#[repr(u16)] +enum Error { + InvalidPhase = 0, +} + +#[no_mangle] +pub extern "C" fn call() { + let phase = runtime::get_phase(); + if phase == Phase::Payment { + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + + let payment_purse: URef = runtime::call_contract( + system::get_proof_of_stake(), + GET_PAYMENT_PURSE, + RuntimeArgs::default(), + ); + + system::transfer_from_purse_to_purse(account::get_main_purse(), payment_purse, amount) + .unwrap_or_revert() + } + + let value: Option<&str> = { + match phase { + Phase::Payment => Some("payment"), + Phase::Session => Some("session"), + _ => None, + } + }; + let value = value.unwrap_or_revert_with(ApiError::User(Error::InvalidPhase as u16)); + let result_key = storage::new_uref(value.to_string()).into(); + let mut uref_name: String = NEW_UREF_RESULT_UREF_NAME.to_string(); + uref_name.push_str("-"); + uref_name.push_str(value); + runtime::put_key(&uref_name, result_key); +} diff --git a/smart-contracts/contracts/test/ee-771-regression/Cargo.toml b/smart-contracts/contracts/test/ee-771-regression/Cargo.toml new file mode 100644 index 0000000000..2fe03118ef --- /dev/null +++ b/smart-contracts/contracts/test/ee-771-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-771-regression" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "ee_771_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-771-regression/src/main.rs b/smart-contracts/contracts/test/ee-771-regression/src/main.rs new file mode 100644 index 0000000000..936fd5c9ea --- /dev/null +++ b/smart-contracts/contracts/test/ee-771-regression/src/main.rs @@ -0,0 +1,98 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::ToString; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + contracts::{NamedKeys, Parameters}, + ApiError, CLType, ContractHash, ContractVersion, EntryPoint, EntryPointAccess, EntryPointType, + EntryPoints, RuntimeArgs, +}; + +const ENTRY_POINT_NAME: &str = "contract_ext"; +const CONTRACT_KEY: &str = "contract"; + +#[no_mangle] +pub extern "C" fn contract_ext() { + match runtime::get_key(CONTRACT_KEY) { + Some(contract_key) => { + // Calls a stored contract if exists. + runtime::call_contract( + contract_key.into_hash().expect("should be a hash"), + "contract_ext", + RuntimeArgs::default(), + ) + } + None => { + // If given key doesn't exist it's the tail call, and an error is triggered. + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + "functiondoesnotexist", + Parameters::default(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + storage::new_contract(entry_points, None, None, None); + } + } +} + +fn store(named_keys: NamedKeys) -> (ContractHash, ContractVersion) { + // extern "C" fn call(named_keys: NamedKeys) { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + ENTRY_POINT_NAME, + Parameters::default(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + storage::new_contract(entry_points, Some(named_keys), None, None) +} + +fn install() -> Result { + let (contract_hash, _contract_version) = store(NamedKeys::new()); + + let mut keys = NamedKeys::new(); + keys.insert(CONTRACT_KEY.to_string(), contract_hash.into()); + let (contract_hash, _contract_version) = store(keys); + + let mut keys_2 = NamedKeys::new(); + keys_2.insert(CONTRACT_KEY.to_string(), contract_hash.into()); + let (contract_hash, _contract_version) = store(keys_2); + + runtime::put_key(CONTRACT_KEY, contract_hash.into()); + + Ok(contract_hash) +} + +fn dispatch(contract_hash: ContractHash) { + runtime::call_contract(contract_hash, "contract_ext", RuntimeArgs::default()) +} + +#[no_mangle] +pub extern "C" fn call() { + let contract_key = install().unwrap_or_revert(); + dispatch(contract_key) +} diff --git a/smart-contracts/contracts/test/ee-803-regression/Cargo.toml b/smart-contracts/contracts/test/ee-803-regression/Cargo.toml new file mode 100644 index 0000000000..fc0e23679e --- /dev/null +++ b/smart-contracts/contracts/test/ee-803-regression/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ee-803-regression" +version = "0.1.0" +authors = ["Bartłomiej Kamiński "] +edition = "2018" + +[[bin]] +name = "ee_803_regression" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/ee-803-regression/src/main.rs b/smart-contracts/contracts/test/ee-803-regression/src/main.rs new file mode 100644 index 0000000000..54ab6d1872 --- /dev/null +++ b/smart-contracts/contracts/test/ee-803-regression/src/main.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; +use contract::contract_api::{runtime, system}; +use types::{runtime_args, ApiError, RuntimeArgs, URef, U512}; + +const ARG_AMOUNT: &str = "amount"; +const ARG_PURSE: &str = "purse"; +const BOND: &str = "bond"; +const UNBOND: &str = "unbond"; +const ARG_ENTRY_POINT_NAME: &str = "method"; + +fn bond(bond_amount: U512, bonding_purse: URef) { + let contract_hash = system::get_proof_of_stake(); + let args = runtime_args! { + ARG_AMOUNT => bond_amount, + ARG_PURSE => bonding_purse, + }; + runtime::call_contract(contract_hash, BOND, args) +} + +fn unbond(unbond_amount: Option) { + let contract_hash = system::get_proof_of_stake(); + let args = runtime_args! { + ARG_AMOUNT => unbond_amount, + }; + runtime::call_contract(contract_hash, UNBOND, args) +} + +#[no_mangle] +pub extern "C" fn call() { + let command: String = runtime::get_named_arg(ARG_ENTRY_POINT_NAME); + match command.as_str() { + BOND => { + let rewards_purse: URef = runtime::get_named_arg(ARG_PURSE); + let available_reward = runtime::get_named_arg(ARG_AMOUNT); + // Attempt to bond using the rewards purse - should not be possible + bond(available_reward, rewards_purse); + } + UNBOND => { + unbond(None); + } + _ => runtime::revert(ApiError::InvalidArgument), + } +} diff --git a/smart-contracts/contracts/test/endless-loop/Cargo.toml b/smart-contracts/contracts/test/endless-loop/Cargo.toml new file mode 100644 index 0000000000..025413c4ea --- /dev/null +++ b/smart-contracts/contracts/test/endless-loop/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "endless-loop" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[[bin]] +name = "endless_loop" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } diff --git a/smart-contracts/contracts/test/endless-loop/src/main.rs b/smart-contracts/contracts/test/endless-loop/src/main.rs new file mode 100644 index 0000000000..9ea5970842 --- /dev/null +++ b/smart-contracts/contracts/test/endless-loop/src/main.rs @@ -0,0 +1,11 @@ +#![no_std] +#![no_main] + +use contract::contract_api::account; + +#[no_mangle] +pub extern "C" fn call() { + loop { + let _main_purse = account::get_main_purse(); + } +} diff --git a/smart-contracts/contracts/test/expensive-calculation/Cargo.toml b/smart-contracts/contracts/test/expensive-calculation/Cargo.toml new file mode 100644 index 0000000000..ea6d5f9a70 --- /dev/null +++ b/smart-contracts/contracts/test/expensive-calculation/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "expensive-calculation" +version = "0.1.0" +authors = ["Bartłomiej Kamiński "] +edition = "2018" + +[[bin]] +name = "expensive_calculation" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/expensive-calculation/src/main.rs b/smart-contracts/contracts/test/expensive-calculation/src/main.rs new file mode 100644 index 0000000000..8e1d1918a9 --- /dev/null +++ b/smart-contracts/contracts/test/expensive-calculation/src/main.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] +#![allow(unused_imports)] + +extern crate alloc; + +use alloc::collections::BTreeMap; +use contract::contract_api::{runtime, storage}; +use types::{ + contracts::Parameters, CLType, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, +}; + +const ENTRY_FUNCTION_NAME: &str = "calculate"; + +#[no_mangle] +pub extern "C" fn calculate() -> u64 { + let large_prime: u64 = 0xffff_fffb; + + let mut result: u64 = 42; + // calculate 42^4242 mod large_prime + for _ in 1..4242 { + result *= 42; + result %= large_prime; + } + + result +} + +#[no_mangle] +pub extern "C" fn call() { + let entry_points = { + let mut entry_points = EntryPoints::new(); + let entry_point = EntryPoint::new( + ENTRY_FUNCTION_NAME, + Parameters::new(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + entry_points + }; + + let (contract_hash, contract_version) = storage::new_contract(entry_points, None, None, None); + runtime::put_key( + "contract_version", + storage::new_uref(contract_version).into(), + ); + runtime::put_key("expensive-calculation", contract_hash.into()); +} diff --git a/smart-contracts/contracts/test/get-arg/Cargo.toml b/smart-contracts/contracts/test/get-arg/Cargo.toml new file mode 100644 index 0000000000..90ef740024 --- /dev/null +++ b/smart-contracts/contracts/test/get-arg/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "get-arg" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "get_arg" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/get-arg/src/main.rs b/smart-contracts/contracts/test/get-arg/src/main.rs new file mode 100644 index 0000000000..f3ecd8deeb --- /dev/null +++ b/smart-contracts/contracts/test/get-arg/src/main.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::contract_api::runtime; +use types::U512; + +const ARG_VALUE0: &str = "value0"; +const ARG_VALUE1: &str = "value1"; + +#[no_mangle] +pub extern "C" fn call() { + let value0: String = runtime::get_named_arg(ARG_VALUE0); + assert_eq!(value0, "Hello, world!"); + + let value1: U512 = runtime::get_named_arg(ARG_VALUE1); + assert_eq!(value1, U512::from(42)); +} diff --git a/smart-contracts/contracts/test/get-blocktime/Cargo.toml b/smart-contracts/contracts/test/get-blocktime/Cargo.toml new file mode 100644 index 0000000000..d921e99812 --- /dev/null +++ b/smart-contracts/contracts/test/get-blocktime/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "get-blocktime" +version = "0.1.0" +authors = ["Ed Hastings , Henry Till "] +edition = "2018" + +[[bin]] +name = "get_blocktime" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/get-blocktime/src/main.rs b/smart-contracts/contracts/test/get-blocktime/src/main.rs new file mode 100644 index 0000000000..11fd1c1ff5 --- /dev/null +++ b/smart-contracts/contracts/test/get-blocktime/src/main.rs @@ -0,0 +1,19 @@ +#![no_std] +#![no_main] + +use contract::contract_api::runtime; +use types::BlockTime; + +const ARG_KNOWN_BLOCK_TIME: &str = "known_block_time"; + +#[no_mangle] +pub extern "C" fn call() { + let known_block_time: u64 = runtime::get_named_arg(ARG_KNOWN_BLOCK_TIME); + let actual_block_time: BlockTime = runtime::get_blocktime(); + + assert_eq!( + actual_block_time, + BlockTime::new(known_block_time), + "actual block time not known block time" + ); +} diff --git a/smart-contracts/contracts/test/get-caller-subcall/Cargo.toml b/smart-contracts/contracts/test/get-caller-subcall/Cargo.toml new file mode 100644 index 0000000000..36489f1d32 --- /dev/null +++ b/smart-contracts/contracts/test/get-caller-subcall/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "get-caller-subcall" +version = "0.1.0" +authors = ["Ed Hastings , Henry Till "] +edition = "2018" + +[[bin]] +name = "get_caller_subcall" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/get-caller-subcall/src/main.rs b/smart-contracts/contracts/test/get-caller-subcall/src/main.rs new file mode 100644 index 0000000000..6a80a99bce --- /dev/null +++ b/smart-contracts/contracts/test/get-caller-subcall/src/main.rs @@ -0,0 +1,65 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{string::ToString, vec::Vec}; + +use alloc::boxed::Box; +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + account::AccountHash, CLType, CLValue, EntryPoint, EntryPointAccess, EntryPointType, + EntryPoints, RuntimeArgs, +}; + +const ENTRY_POINT_NAME: &str = "get_caller_ext"; +const HASH_KEY_NAME: &str = "caller_subcall"; +const ACCESS_KEY_NAME: &str = "caller_subcall_access"; +const ARG_ACCOUNT: &str = "account"; + +#[no_mangle] +pub extern "C" fn get_caller_ext() { + let caller_account_hash: AccountHash = runtime::get_caller(); + runtime::ret(CLValue::from_t(caller_account_hash).unwrap_or_revert()); +} + +#[no_mangle] +pub extern "C" fn call() { + let known_account_hash: AccountHash = runtime::get_named_arg(ARG_ACCOUNT); + let caller_account_hash: AccountHash = runtime::get_caller(); + assert_eq!( + caller_account_hash, known_account_hash, + "caller account hash was not known account hash" + ); + + let entry_points = { + let mut entry_points = EntryPoints::new(); + // takes no args, ret's PublicKey + let entry_point = EntryPoint::new( + ENTRY_POINT_NAME.to_string(), + Vec::new(), + CLType::FixedList(Box::new(CLType::U8), 32), + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + entry_points + }; + + let (contract_hash, _contract_version) = storage::new_contract( + entry_points, + None, + Some(HASH_KEY_NAME.to_string()), + Some(ACCESS_KEY_NAME.to_string()), + ); + + let subcall_account_hash: AccountHash = + runtime::call_contract(contract_hash, ENTRY_POINT_NAME, RuntimeArgs::default()); + assert_eq!( + subcall_account_hash, known_account_hash, + "subcall account hash was not known account hash" + ); +} diff --git a/smart-contracts/contracts/test/get-caller/Cargo.toml b/smart-contracts/contracts/test/get-caller/Cargo.toml new file mode 100644 index 0000000000..3aac9f23ba --- /dev/null +++ b/smart-contracts/contracts/test/get-caller/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "get-caller" +version = "0.1.0" +authors = ["Ed Hastings , Henry Till "] +edition = "2018" + +[[bin]] +name = "get_caller" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/get-caller/src/main.rs b/smart-contracts/contracts/test/get-caller/src/main.rs new file mode 100644 index 0000000000..176226d114 --- /dev/null +++ b/smart-contracts/contracts/test/get-caller/src/main.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +use contract::contract_api::runtime; +use types::account::AccountHash; + +const ARG_ACCOUNT: &str = "account"; + +#[no_mangle] +pub extern "C" fn call() { + let known_account_hash: AccountHash = runtime::get_named_arg(ARG_ACCOUNT); + let caller_account_hash: AccountHash = runtime::get_caller(); + assert_eq!( + caller_account_hash, known_account_hash, + "caller account hash was not known account hash" + ); +} diff --git a/smart-contracts/contracts/test/get-phase-payment/Cargo.toml b/smart-contracts/contracts/test/get-phase-payment/Cargo.toml new file mode 100644 index 0000000000..804264babc --- /dev/null +++ b/smart-contracts/contracts/test/get-phase-payment/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "get-phase-payment" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "get_phase_payment" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/get-phase-payment/src/main.rs b/smart-contracts/contracts/test/get-phase-payment/src/main.rs new file mode 100644 index 0000000000..249956a995 --- /dev/null +++ b/smart-contracts/contracts/test/get-phase-payment/src/main.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{Phase, RuntimeArgs, URef, U512}; + +const GET_PAYMENT_PURSE: &str = "get_payment_purse"; +const ARG_PHASE: &str = "phase"; + +fn standard_payment(amount: U512) { + let main_purse = account::get_main_purse(); + + let pos_pointer = system::get_proof_of_stake(); + + let payment_purse: URef = + runtime::call_contract(pos_pointer, GET_PAYMENT_PURSE, RuntimeArgs::default()); + + system::transfer_from_purse_to_purse(main_purse, payment_purse, amount).unwrap_or_revert() +} + +#[no_mangle] +pub extern "C" fn call() { + let known_phase: Phase = runtime::get_named_arg(ARG_PHASE); + let get_phase = runtime::get_phase(); + assert_eq!( + get_phase, known_phase, + "get_phase did not return known_phase" + ); + + standard_payment(U512::from(10_000_000)); +} diff --git a/smart-contracts/contracts/test/get-phase/Cargo.toml b/smart-contracts/contracts/test/get-phase/Cargo.toml new file mode 100644 index 0000000000..8f7117758a --- /dev/null +++ b/smart-contracts/contracts/test/get-phase/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "get-phase" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "get_phase" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/get-phase/src/main.rs b/smart-contracts/contracts/test/get-phase/src/main.rs new file mode 100644 index 0000000000..f426d34b7a --- /dev/null +++ b/smart-contracts/contracts/test/get-phase/src/main.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +use contract::contract_api::runtime; +use types::Phase; + +const ARG_PHASE: &str = "phase"; + +#[no_mangle] +pub extern "C" fn call() { + let known_phase: Phase = runtime::get_named_arg(ARG_PHASE); + let get_phase = runtime::get_phase(); + assert_eq!( + get_phase, known_phase, + "get_phase did not return known_phase" + ); +} diff --git a/smart-contracts/contracts/test/groups/Cargo.toml b/smart-contracts/contracts/test/groups/Cargo.toml new file mode 100644 index 0000000000..8bdf0d63ac --- /dev/null +++ b/smart-contracts/contracts/test/groups/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "groups" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "groups" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/groups/src/main.rs b/smart-contracts/contracts/test/groups/src/main.rs new file mode 100644 index 0000000000..f475339685 --- /dev/null +++ b/smart-contracts/contracts/test/groups/src/main.rs @@ -0,0 +1,235 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate alloc; + +use alloc::{collections::BTreeSet, string::ToString, vec::Vec}; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + contracts::{ + EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, NamedKeys, + CONTRACT_INITIAL_VERSION, + }, + runtime_args, CLType, ContractPackageHash, Key, Parameter, RuntimeArgs, URef, +}; + +const PACKAGE_HASH_KEY: &str = "package_hash_key"; +const PACKAGE_ACCESS_KEY: &str = "package_access_key"; +const RESTRICTED_CONTRACT: &str = "restricted_contract"; +const RESTRICTED_SESSION: &str = "restricted_session"; +const RESTRICTED_SESSION_CALLER: &str = "restricted_session_caller"; +const UNRESTRICTED_CONTRACT_CALLER: &str = "unrestricted_contract_caller"; +const RESTRICTED_CONTRACT_CALLER_AS_SESSION: &str = "restricted_contract_caller_as_session"; +const UNCALLABLE_SESSION: &str = "uncallable_session"; +const UNCALLABLE_CONTRACT: &str = "uncallable_contract"; +const CALL_RESTRICTED_ENTRY_POINTS: &str = "call_restricted_entry_points"; +const ARG_PACKAGE_HASH: &str = "package_hash"; + +#[no_mangle] +pub extern "C" fn restricted_session() {} + +#[no_mangle] +pub extern "C" fn restricted_contract() {} + +#[no_mangle] +pub extern "C" fn restricted_session_caller() { + let package_hash: Key = runtime::get_named_arg(ARG_PACKAGE_HASH); + let contract_version = Some(CONTRACT_INITIAL_VERSION); + runtime::call_versioned_contract( + package_hash.into_seed(), + contract_version, + RESTRICTED_SESSION, + runtime_args! {}, + ) +} + +fn contract_caller() { + let package_hash: Key = runtime::get_named_arg(ARG_PACKAGE_HASH); + let contract_package_hash = package_hash.into_seed(); + let contract_version = Some(CONTRACT_INITIAL_VERSION); + let runtime_args = runtime_args! {}; + runtime::call_versioned_contract( + contract_package_hash, + contract_version, + RESTRICTED_CONTRACT, + runtime_args, + ) +} + +#[no_mangle] +pub extern "C" fn unrestricted_contract_caller() { + contract_caller(); +} + +#[no_mangle] +pub extern "C" fn restricted_contract_caller_as_session() { + contract_caller(); +} + +#[no_mangle] +pub extern "C" fn uncallable_session() {} + +#[no_mangle] +pub extern "C" fn uncallable_contract() {} + +#[no_mangle] +pub extern "C" fn call_restricted_entry_points() { + // We're aggresively removing exports that aren't exposed through contract header so test + // ensures that those exports are still inside WASM. + uncallable_session(); + uncallable_contract(); +} + +fn create_group(package_hash: ContractPackageHash) -> URef { + let new_uref_1 = storage::new_uref(()); + runtime::put_key("saved_uref", new_uref_1.into()); + + let mut existing_urefs = BTreeSet::new(); + existing_urefs.insert(new_uref_1); + + let new_urefs = storage::create_contract_user_group(package_hash, "Group 1", 1, existing_urefs) + .unwrap_or_revert(); + assert_eq!(new_urefs.len(), 1); + new_urefs[0] +} + +/// Restricted uref comes from creating a group and will be assigned to a smart contract +fn create_entry_points_1() -> EntryPoints { + let mut entry_points = EntryPoints::new(); + let restricted_session = EntryPoint::new( + RESTRICTED_SESSION.to_string(), + Vec::new(), + CLType::I32, + EntryPointAccess::groups(&["Group 1"]), + EntryPointType::Session, + ); + entry_points.add_entry_point(restricted_session); + + let restricted_contract = EntryPoint::new( + RESTRICTED_CONTRACT.to_string(), + Vec::new(), + CLType::I32, + EntryPointAccess::groups(&["Group 1"]), + EntryPointType::Contract, + ); + entry_points.add_entry_point(restricted_contract); + + let restricted_session_caller = EntryPoint::new( + RESTRICTED_SESSION_CALLER.to_string(), + vec![Parameter::new(ARG_PACKAGE_HASH, CLType::Key)], + CLType::I32, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(restricted_session_caller); + + let restricted_contract = EntryPoint::new( + RESTRICTED_CONTRACT.to_string(), + Vec::new(), + CLType::I32, + EntryPointAccess::groups(&["Group 1"]), + EntryPointType::Contract, + ); + entry_points.add_entry_point(restricted_contract); + + let unrestricted_contract_caller = EntryPoint::new( + UNRESTRICTED_CONTRACT_CALLER.to_string(), + Vec::new(), + CLType::I32, + // Made public because we've tested deploy level auth into a contract in + // RESTRICTED_CONTRACT entrypoint + EntryPointAccess::Public, + // NOTE: Public contract authorizes any contract call, because this contract has groups + // uref in its named keys + EntryPointType::Contract, + ); + entry_points.add_entry_point(unrestricted_contract_caller); + + let unrestricted_contract_caller_as_session = EntryPoint::new( + RESTRICTED_CONTRACT_CALLER_AS_SESSION.to_string(), + Vec::new(), + CLType::I32, + // Made public because we've tested deploy level auth into a contract in + // RESTRICTED_CONTRACT entrypoint + EntryPointAccess::Public, + // NOTE: Public contract authorizes any contract call, because this contract has groups + // uref in its named keys + EntryPointType::Session, + ); + entry_points.add_entry_point(unrestricted_contract_caller_as_session); + + let uncallable_session = EntryPoint::new( + UNCALLABLE_SESSION.to_string(), + Vec::new(), + CLType::I32, + // Made public because we've tested deploy level auth into a contract in + // RESTRICTED_CONTRACT entrypoint + EntryPointAccess::groups(&[]), + // NOTE: Public contract authorizes any contract call, because this contract has groups + // uref in its named keys + EntryPointType::Session, + ); + entry_points.add_entry_point(uncallable_session); + + let uncallable_contract = EntryPoint::new( + UNCALLABLE_CONTRACT.to_string(), + Vec::new(), + CLType::I32, + // Made public because we've tested deploy level auth into a contract in + // RESTRICTED_CONTRACT entrypoint + EntryPointAccess::groups(&[]), + // NOTE: Public contract authorizes any contract call, because this contract has groups + // uref in its named keys + EntryPointType::Session, + ); + entry_points.add_entry_point(uncallable_contract); + + // Directly calls entry_points that are protected with empty group of lists to verify that even + // though they're not callable externally, they're still visible in the WASM. + let call_restricted_entry_points = EntryPoint::new( + CALL_RESTRICTED_ENTRY_POINTS.to_string(), + Vec::new(), + CLType::I32, + // Made public because we've tested deploy level auth into a contract in + // RESTRICTED_CONTRACT entrypoint + EntryPointAccess::Public, + // NOTE: Public contract authorizes any contract call, because this contract has groups + // uref in its named keys + EntryPointType::Session, + ); + entry_points.add_entry_point(call_restricted_entry_points); + + entry_points +} + +fn install_version_1(contract_package_hash: ContractPackageHash, restricted_uref: URef) { + let contract_named_keys = { + let contract_variable = storage::new_uref(0); + + let mut named_keys = NamedKeys::new(); + named_keys.insert("contract_named_key".to_string(), contract_variable.into()); + named_keys.insert("restricted_uref".to_string(), restricted_uref.into()); + named_keys + }; + + let entry_points = create_entry_points_1(); + storage::add_contract_version(contract_package_hash, entry_points, contract_named_keys); +} + +#[no_mangle] +pub extern "C" fn call() { + // Session contract + let (contract_package_hash, access_uref) = storage::create_contract_package_at_hash(); + + runtime::put_key(PACKAGE_HASH_KEY, contract_package_hash.into()); + runtime::put_key(PACKAGE_ACCESS_KEY, access_uref.into()); + + let restricted_uref = create_group(contract_package_hash); + + install_version_1(contract_package_hash, restricted_uref); +} diff --git a/smart-contracts/contracts/test/key-management-thresholds/Cargo.toml b/smart-contracts/contracts/test/key-management-thresholds/Cargo.toml new file mode 100644 index 0000000000..63ae19bf23 --- /dev/null +++ b/smart-contracts/contracts/test/key-management-thresholds/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "key-management-thresholds" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "key_management_thresholds" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/key-management-thresholds/src/main.rs b/smart-contracts/contracts/test/key-management-thresholds/src/main.rs new file mode 100644 index 0000000000..498c81dcee --- /dev/null +++ b/smart-contracts/contracts/test/key-management-thresholds/src/main.rs @@ -0,0 +1,73 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::{ + contract_api::{account, runtime}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + account::{ + AccountHash, ActionType, AddKeyFailure, RemoveKeyFailure, SetThresholdFailure, + UpdateKeyFailure, Weight, + }, + ApiError, +}; + +const ARG_STAGE: &str = "stage"; +#[no_mangle] +pub extern "C" fn call() { + let stage: String = runtime::get_named_arg(ARG_STAGE); + + if stage == "init" { + // executed with weight >= 1 + account::add_associated_key(AccountHash::new([42; 32]), Weight::new(100)) + .unwrap_or_revert(); + // this key will be used to test permission denied when removing keys with low + // total weight + account::add_associated_key(AccountHash::new([43; 32]), Weight::new(1)).unwrap_or_revert(); + account::add_associated_key(AccountHash::new([1; 32]), Weight::new(1)).unwrap_or_revert(); + account::set_action_threshold(ActionType::KeyManagement, Weight::new(101)) + .unwrap_or_revert(); + } else if stage == "test-permission-denied" { + // Has to be executed with keys of total weight < 255 + match account::add_associated_key(AccountHash::new([44; 32]), Weight::new(1)) { + Ok(_) => runtime::revert(ApiError::User(200)), + Err(AddKeyFailure::PermissionDenied) => {} + Err(_) => runtime::revert(ApiError::User(201)), + } + + match account::update_associated_key(AccountHash::new([43; 32]), Weight::new(2)) { + Ok(_) => runtime::revert(ApiError::User(300)), + Err(UpdateKeyFailure::PermissionDenied) => {} + Err(_) => runtime::revert(ApiError::User(301)), + } + match account::remove_associated_key(AccountHash::new([43; 32])) { + Ok(_) => runtime::revert(ApiError::User(400)), + Err(RemoveKeyFailure::PermissionDenied) => {} + Err(_) => runtime::revert(ApiError::User(401)), + } + + match account::set_action_threshold(ActionType::KeyManagement, Weight::new(255)) { + Ok(_) => runtime::revert(ApiError::User(500)), + Err(SetThresholdFailure::PermissionDeniedError) => {} + Err(_) => runtime::revert(ApiError::User(501)), + } + } else if stage == "test-key-mgmnt-succeed" { + // Has to be executed with keys of total weight >= 254 + account::add_associated_key(AccountHash::new([44; 32]), Weight::new(1)).unwrap_or_revert(); + // Updates [43;32] key weight created in init stage + account::update_associated_key(AccountHash::new([44; 32]), Weight::new(2)) + .unwrap_or_revert(); + // Removes [43;32] key created in init stage + account::remove_associated_key(AccountHash::new([44; 32])).unwrap_or_revert(); + // Sets action threshodl + account::set_action_threshold(ActionType::KeyManagement, Weight::new(100)) + .unwrap_or_revert(); + } else { + runtime::revert(ApiError::User(1)) + } +} diff --git a/smart-contracts/contracts/test/list-named-keys/Cargo.toml b/smart-contracts/contracts/test/list-named-keys/Cargo.toml new file mode 100644 index 0000000000..1b78383135 --- /dev/null +++ b/smart-contracts/contracts/test/list-named-keys/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "list-named-keys" +version = "0.1.0" +authors = ["Fraser Hutchison "] +edition = "2018" + +[[bin]] +name = "list_named_keys" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/list-named-keys/src/main.rs b/smart-contracts/contracts/test/list-named-keys/src/main.rs new file mode 100644 index 0000000000..2b098164d8 --- /dev/null +++ b/smart-contracts/contracts/test/list-named-keys/src/main.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{string::String, vec::Vec}; + +use contract::contract_api::runtime; +use types::contracts::NamedKeys; + +const ARG_INITIAL_NAMED_KEYS: &str = "initial_named_args"; +const ARG_NEW_NAMED_KEYS: &str = "new_named_keys"; + +#[no_mangle] +pub extern "C" fn call() { + // Account starts with two known named keys: mint uref & pos uref. + let expected_initial_named_keys: NamedKeys = runtime::get_named_arg(ARG_INITIAL_NAMED_KEYS); + + let actual_named_keys = runtime::list_named_keys(); + assert_eq!(expected_initial_named_keys, actual_named_keys); + + // Add further named keys and assert that each is returned in `list_named_keys()`. + let new_named_keys: NamedKeys = runtime::get_named_arg(ARG_NEW_NAMED_KEYS); + let mut expected_named_keys = expected_initial_named_keys; + + for (key, value) in new_named_keys { + runtime::put_key(&key, value); + assert!(expected_named_keys.insert(key, value).is_none()); + let actual_named_keys = runtime::list_named_keys(); + assert_eq!(expected_named_keys, actual_named_keys); + } + + // Remove all named keys and check that removed keys aren't returned in `list_named_keys()`. + let all_key_names: Vec = expected_named_keys.keys().cloned().collect(); + for key in all_key_names { + runtime::remove_key(&key); + assert!(expected_named_keys.remove(&key).is_some()); + let actual_named_keys = runtime::list_named_keys(); + assert_eq!(expected_named_keys, actual_named_keys); + } +} diff --git a/smart-contracts/contracts/test/local-state-add/Cargo.toml b/smart-contracts/contracts/test/local-state-add/Cargo.toml new file mode 100644 index 0000000000..274fb3a328 --- /dev/null +++ b/smart-contracts/contracts/test/local-state-add/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "local-state-add" +version = "0.1.0" +authors = ["Bartłomiej Kamiński "] +edition = "2018" + +[[bin]] +name = "local_state_add" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/local-state-add/src/main.rs b/smart-contracts/contracts/test/local-state-add/src/main.rs new file mode 100644 index 0000000000..ced3581919 --- /dev/null +++ b/smart-contracts/contracts/test/local-state-add/src/main.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::contract_api::{runtime, storage}; + +pub const LOCAL_KEY: [u8; 32] = [66u8; 32]; + +const CMD_WRITE: &str = "write"; +const CMD_ADD: &str = "add"; + +const INITIAL_VALUE: u64 = 10; +const ADD_VALUE: u64 = 5; + +const ARG_COMMAND: &str = "command"; + +#[no_mangle] +pub extern "C" fn call() { + let command: String = runtime::get_named_arg(ARG_COMMAND); + + if command == CMD_WRITE { + storage::write_local(LOCAL_KEY, INITIAL_VALUE); + } else if command == CMD_ADD { + storage::add_local(LOCAL_KEY, ADD_VALUE); + } +} diff --git a/smart-contracts/contracts/test/local-state-stored-caller/Cargo.toml b/smart-contracts/contracts/test/local-state-stored-caller/Cargo.toml new file mode 100644 index 0000000000..5cf4f0c79f --- /dev/null +++ b/smart-contracts/contracts/test/local-state-stored-caller/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "local-state-stored-caller" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "local_state_stored_caller" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/local-state-stored-caller/src/main.rs b/smart-contracts/contracts/test/local-state-stored-caller/src/main.rs new file mode 100644 index 0000000000..b7b108bb80 --- /dev/null +++ b/smart-contracts/contracts/test/local-state-stored-caller/src/main.rs @@ -0,0 +1,15 @@ +#![no_std] +#![no_main] + +use contract::contract_api::runtime; +use types::{ContractHash, RuntimeArgs}; + +const ARG_SEED: &str = "seed"; +const ENTRY_FUNCTION_NAME: &str = "delegate"; + +#[no_mangle] +pub extern "C" fn call() { + let contract_hash: ContractHash = runtime::get_named_arg(ARG_SEED); + + runtime::call_contract(contract_hash, ENTRY_FUNCTION_NAME, RuntimeArgs::default()) +} diff --git a/smart-contracts/contracts/test/local-state-stored-upgraded/Cargo.toml b/smart-contracts/contracts/test/local-state-stored-upgraded/Cargo.toml new file mode 100644 index 0000000000..3ea40820dc --- /dev/null +++ b/smart-contracts/contracts/test/local-state-stored-upgraded/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "local-state-stored-upgraded" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "local_state_stored_upgraded" +path = "src/bin/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } +local-state = { path = "../local-state" } diff --git a/smart-contracts/contracts/test/local-state-stored-upgraded/src/bin/main.rs b/smart-contracts/contracts/test/local-state-stored-upgraded/src/bin/main.rs new file mode 100644 index 0000000000..fcf957fc94 --- /dev/null +++ b/smart-contracts/contracts/test/local-state-stored-upgraded/src/bin/main.rs @@ -0,0 +1,7 @@ +#![no_std] +#![no_main] + +#[no_mangle] +pub extern "C" fn call() { + local_state_stored_upgraded::delegate(); +} diff --git a/smart-contracts/contracts/test/local-state-stored-upgraded/src/lib.rs b/smart-contracts/contracts/test/local-state-stored-upgraded/src/lib.rs new file mode 100644 index 0000000000..019494b98e --- /dev/null +++ b/smart-contracts/contracts/test/local-state-stored-upgraded/src/lib.rs @@ -0,0 +1,45 @@ +#![no_std] + +extern crate alloc; + +use alloc::string::String; + +use contract::{contract_api::storage, unwrap_or_revert::UnwrapOrRevert}; +use types::ApiError; + +pub const ENTRY_FUNCTION_NAME: &str = "delegate"; +pub const CONTRACT_NAME: &str = "local_state_stored"; +pub const SNIPPET: &str = " I've been upgraded!"; + +#[repr(u16)] +enum CustomError { + LocalKeyReadMutatedBytesRepr = 1, + UnableToReadMutatedLocalKey = 2, +} + +pub fn delegate() { + local_state::delegate(); + // read from local state + let mut res: String = storage::read_local(&local_state::LOCAL_KEY) + .unwrap_or_default() + .unwrap_or_default(); + + res.push_str(SNIPPET); + // Write "Hello, " + storage::write_local(local_state::LOCAL_KEY, res); + + // Read back + let res: String = storage::read_local(&local_state::LOCAL_KEY) + .unwrap_or_revert_with(ApiError::User( + CustomError::UnableToReadMutatedLocalKey as u16, + )) + .unwrap_or_revert_with(ApiError::User( + CustomError::LocalKeyReadMutatedBytesRepr as u16, + )); + + // local state should be available after upgrade + assert!( + !res.is_empty(), + "local value should be accessible post upgrade" + ) +} diff --git a/smart-contracts/contracts/test/local-state-stored-upgrader/Cargo.toml b/smart-contracts/contracts/test/local-state-stored-upgrader/Cargo.toml new file mode 100644 index 0000000000..3357573bfd --- /dev/null +++ b/smart-contracts/contracts/test/local-state-stored-upgrader/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "local-state-stored-upgrader" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "local_state_stored_upgrader" +path = "src/bin/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } +local-state-stored-upgraded = { path = "../local-state-stored-upgraded" } diff --git a/smart-contracts/contracts/test/local-state-stored-upgrader/src/bin/main.rs b/smart-contracts/contracts/test/local-state-stored-upgrader/src/bin/main.rs new file mode 100644 index 0000000000..ae0f8a2a46 --- /dev/null +++ b/smart-contracts/contracts/test/local-state-stored-upgrader/src/bin/main.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + contracts::{NamedKeys, Parameters}, + CLType, ContractHash, ContractPackageHash, ContractVersion, EntryPoint, EntryPointAccess, + EntryPointType, EntryPoints, +}; + +const CONTRACT_NAME: &str = "local_state_stored"; +const ENTRY_FUNCTION_NAME: &str = "delegate"; +const CONTRACT_PACKAGE_KEY: &str = "contract_package"; +const CONTRACT_ACCESS_KEY: &str = "access_key"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[no_mangle] +pub extern "C" fn delegate() { + local_state_stored_upgraded::delegate() +} + +fn upgrade(contract_package_hash: ContractPackageHash) -> (ContractHash, ContractVersion) { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + ENTRY_FUNCTION_NAME, + Parameters::new(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + + storage::add_contract_version(contract_package_hash, entry_points, NamedKeys::new()) +} + +#[no_mangle] +pub extern "C" fn call() { + let contract_package_hash = runtime::get_named_arg(CONTRACT_PACKAGE_KEY); + let _access_key = runtime::get_key(CONTRACT_ACCESS_KEY) + .unwrap_or_revert() + .into_uref() + .unwrap_or_revert(); + let (contract_hash, contract_version) = upgrade(contract_package_hash); + runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into()); + runtime::put_key(CONTRACT_NAME, contract_hash.into()); +} diff --git a/smart-contracts/contracts/test/local-state-stored/Cargo.toml b/smart-contracts/contracts/test/local-state-stored/Cargo.toml new file mode 100644 index 0000000000..f47d08c8f0 --- /dev/null +++ b/smart-contracts/contracts/test/local-state-stored/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "local-state-stored" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "local_state_stored" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } +local-state = { path = "../local-state" } diff --git a/smart-contracts/contracts/test/local-state-stored/src/main.rs b/smart-contracts/contracts/test/local-state-stored/src/main.rs new file mode 100644 index 0000000000..ff735e80ea --- /dev/null +++ b/smart-contracts/contracts/test/local-state-stored/src/main.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::ToString; + +use contract::contract_api::{runtime, storage}; +use types::{ + contracts::Parameters, CLType, ContractHash, ContractVersion, EntryPoint, EntryPointAccess, + EntryPointType, EntryPoints, +}; + +const ENTRY_FUNCTION_NAME: &str = "delegate"; +const CONTRACT_NAME: &str = "local_state_stored"; +const CONTRACT_PACKAGE_KEY: &str = "contract_package"; +const CONTRACT_ACCESS_KEY: &str = "access_key"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[no_mangle] +pub extern "C" fn delegate() { + local_state::delegate() +} + +fn store() -> (ContractHash, ContractVersion) { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + ENTRY_FUNCTION_NAME, + Parameters::new(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + + entry_points.add_entry_point(entry_point); + + entry_points + }; + storage::new_contract( + entry_points, + None, + Some(CONTRACT_PACKAGE_KEY.to_string()), + Some(CONTRACT_ACCESS_KEY.to_string()), + ) +} + +#[no_mangle] +pub extern "C" fn call() { + let (contract_hash, contract_version) = store(); + runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into()); + runtime::put_key(CONTRACT_NAME, contract_hash.into()); +} diff --git a/smart-contracts/contracts/test/local-state/Cargo.toml b/smart-contracts/contracts/test/local-state/Cargo.toml new file mode 100644 index 0000000000..bea6de9486 --- /dev/null +++ b/smart-contracts/contracts/test/local-state/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "local-state" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "local_state" +path = "src/bin/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } diff --git a/smart-contracts/contracts/test/local-state/src/bin/main.rs b/smart-contracts/contracts/test/local-state/src/bin/main.rs new file mode 100644 index 0000000000..6140d3490c --- /dev/null +++ b/smart-contracts/contracts/test/local-state/src/bin/main.rs @@ -0,0 +1,7 @@ +#![no_std] +#![no_main] + +#[no_mangle] +pub extern "C" fn call() { + local_state::delegate() +} diff --git a/smart-contracts/contracts/test/local-state/src/lib.rs b/smart-contracts/contracts/test/local-state/src/lib.rs new file mode 100644 index 0000000000..c55e90a55c --- /dev/null +++ b/smart-contracts/contracts/test/local-state/src/lib.rs @@ -0,0 +1,33 @@ +#![no_std] + +extern crate alloc; + +use alloc::string::{String, ToString}; + +use contract::{contract_api::storage, unwrap_or_revert::UnwrapOrRevert}; + +pub const LOCAL_KEY: [u8; 32] = [66u8; 32]; +pub const HELLO_PREFIX: &str = " Hello, "; +pub const WORLD_SUFFIX: &str = "world!"; + +pub fn delegate() { + // Appends " Hello, world!" to a [66; 32] local key with spaces trimmed. + // Two runs should yield value "Hello, world! Hello, world!" + // read from local state + let mut res: String = storage::read_local(&LOCAL_KEY) + .unwrap_or_default() + .unwrap_or_default(); + + res.push_str(HELLO_PREFIX); + // Write "Hello, " + storage::write_local(LOCAL_KEY, res); + + // Read (this should exercise cache) + let mut res: String = storage::read_local(&LOCAL_KEY) + .unwrap_or_revert() + .unwrap_or_revert(); + // Append + res.push_str(WORLD_SUFFIX); + // Write + storage::write_local(LOCAL_KEY, res.trim().to_string()); +} diff --git a/smart-contracts/contracts/test/main-purse/Cargo.toml b/smart-contracts/contracts/test/main-purse/Cargo.toml new file mode 100644 index 0000000000..9bd46518ef --- /dev/null +++ b/smart-contracts/contracts/test/main-purse/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "main-purse" +version = "0.1.0" +authors = ["Ed Hastings , Henry Till "] +edition = "2018" + +[[bin]] +name = "main_purse" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/main-purse/src/main.rs b/smart-contracts/contracts/test/main-purse/src/main.rs new file mode 100644 index 0000000000..2ebbac32d7 --- /dev/null +++ b/smart-contracts/contracts/test/main-purse/src/main.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +use contract::contract_api::{account, runtime}; +use types::URef; + +const ARG_PURSE: &str = "purse"; + +#[no_mangle] +pub extern "C" fn call() { + let known_main_purse: URef = runtime::get_named_arg(ARG_PURSE); + let main_purse: URef = account::get_main_purse(); + assert_eq!( + main_purse, known_main_purse, + "main purse was not known purse" + ); +} diff --git a/smart-contracts/contracts/test/manage-groups/Cargo.toml b/smart-contracts/contracts/test/manage-groups/Cargo.toml new file mode 100644 index 0000000000..d94b62411e --- /dev/null +++ b/smart-contracts/contracts/test/manage-groups/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "manage-groups" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "manage_groups" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/manage-groups/src/main.rs b/smart-contracts/contracts/test/manage-groups/src/main.rs new file mode 100644 index 0000000000..76070626ba --- /dev/null +++ b/smart-contracts/contracts/test/manage-groups/src/main.rs @@ -0,0 +1,166 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate alloc; + +use alloc::{ + boxed::Box, + collections::BTreeSet, + string::{String, ToString}, + vec::Vec, +}; + +use core::{convert::TryInto, iter::FromIterator}; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + contracts::{EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, NamedKeys}, + CLType, ContractPackageHash, Key, Parameter, URef, +}; + +const PACKAGE_HASH_KEY: &str = "package_hash_key"; +const PACKAGE_ACCESS_KEY: &str = "package_access_key"; +const CREATE_GROUP: &str = "create_group"; +const REMOVE_GROUP: &str = "remove_group"; +const EXTEND_GROUP_UREFS: &str = "extend_group_urefs"; +const REMOVE_GROUP_UREFS: &str = "remove_group_urefs"; +const GROUP_NAME_ARG: &str = "group_name"; +const UREFS_ARG: &str = "urefs"; +const TOTAL_NEW_UREFS_ARG: &str = "total_new_urefs"; +const TOTAL_EXISTING_UREFS_ARG: &str = "total_existing_urefs"; + +#[no_mangle] +pub extern "C" fn create_group() { + let package_hash_key: ContractPackageHash = runtime::get_key(PACKAGE_HASH_KEY) + .and_then(Key::into_hash) + .unwrap_or_revert(); + let group_name: String = runtime::get_named_arg(GROUP_NAME_ARG); + let total_urefs: u64 = runtime::get_named_arg(TOTAL_NEW_UREFS_ARG); + let total_existing_urefs: u64 = runtime::get_named_arg(TOTAL_EXISTING_UREFS_ARG); + let existing_urefs: Vec = (0..total_existing_urefs).map(storage::new_uref).collect(); + + let _new_uref = storage::create_contract_user_group( + package_hash_key, + &group_name, + total_urefs as u8, + BTreeSet::from_iter(existing_urefs), + ) + .unwrap_or_revert(); +} + +#[no_mangle] +pub extern "C" fn remove_group() { + let package_hash_key: ContractPackageHash = runtime::get_key(PACKAGE_HASH_KEY) + .and_then(Key::into_hash) + .unwrap_or_revert(); + let group_name: String = runtime::get_named_arg(GROUP_NAME_ARG); + storage::remove_contract_user_group(package_hash_key, &group_name).unwrap_or_revert(); +} + +#[no_mangle] +pub extern "C" fn extend_group_urefs() { + let package_hash_key: ContractPackageHash = runtime::get_key(PACKAGE_HASH_KEY) + .and_then(Key::into_hash) + .unwrap_or_revert(); + let group_name: String = runtime::get_named_arg(GROUP_NAME_ARG); + let new_urefs_count: u64 = runtime::get_named_arg(TOTAL_NEW_UREFS_ARG); + + // Provisions additional urefs inside group + for _ in 1..=new_urefs_count { + let _new_uref = storage::provision_contract_user_group_uref(package_hash_key, &group_name) + .unwrap_or_revert(); + } +} + +#[no_mangle] +pub extern "C" fn remove_group_urefs() { + let package_hash_key: ContractPackageHash = runtime::get_key(PACKAGE_HASH_KEY) + .and_then(Key::into_hash) + .unwrap_or_revert(); + let _package_access_key: URef = runtime::get_key(PACKAGE_ACCESS_KEY) + .unwrap_or_revert() + .try_into() + .unwrap(); + let group_name: String = runtime::get_named_arg(GROUP_NAME_ARG); + let urefs: Vec = runtime::get_named_arg(UREFS_ARG); + storage::remove_contract_user_group_urefs( + package_hash_key, + &group_name, + BTreeSet::from_iter(urefs), + ) + .unwrap_or_revert(); +} + +/// Restricted uref comes from creating a group and will be assigned to a smart contract +fn create_entry_points_1() -> EntryPoints { + let mut entry_points = EntryPoints::new(); + let restricted_session = EntryPoint::new( + CREATE_GROUP.to_string(), + vec![ + Parameter::new(GROUP_NAME_ARG, CLType::String), + Parameter::new(TOTAL_EXISTING_UREFS_ARG, CLType::U64), + Parameter::new(TOTAL_NEW_UREFS_ARG, CLType::U64), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(restricted_session); + + let remove_group = EntryPoint::new( + REMOVE_GROUP.to_string(), + vec![Parameter::new(GROUP_NAME_ARG, CLType::String)], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(remove_group); + + let entry_point_name = EXTEND_GROUP_UREFS.to_string(); + let extend_group_urefs = EntryPoint::new( + entry_point_name, + vec![ + Parameter::new(GROUP_NAME_ARG, CLType::String), + Parameter::new(TOTAL_NEW_UREFS_ARG, CLType::U64), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(extend_group_urefs); + + let entry_point_name = REMOVE_GROUP_UREFS.to_string(); + let remove_group_urefs = EntryPoint::new( + entry_point_name, + vec![ + Parameter::new(GROUP_NAME_ARG, CLType::String), + Parameter::new(UREFS_ARG, CLType::List(Box::new(CLType::URef))), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(remove_group_urefs); + entry_points +} + +fn install_version_1(package_hash: ContractPackageHash) { + let contract_named_keys = NamedKeys::new(); + + let entry_points = create_entry_points_1(); + storage::add_contract_version(package_hash, entry_points, contract_named_keys); +} + +#[no_mangle] +pub extern "C" fn call() { + let (package_hash, access_uref) = storage::create_contract_package_at_hash(); + + runtime::put_key(PACKAGE_HASH_KEY, package_hash.into()); + runtime::put_key(PACKAGE_ACCESS_KEY, access_uref.into()); + + install_version_1(package_hash); +} diff --git a/smart-contracts/contracts/test/measure-gas-subcall/Cargo.toml b/smart-contracts/contracts/test/measure-gas-subcall/Cargo.toml new file mode 100644 index 0000000000..f0c74d6b0e --- /dev/null +++ b/smart-contracts/contracts/test/measure-gas-subcall/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "measure-gas-subcall" +version = "0.1.0" +authors = ["Bartłomiej Kamiński "] +edition = "2018" + +[[bin]] +name = "measure_gas_subcall" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/measure-gas-subcall/src/main.rs b/smart-contracts/contracts/test/measure-gas-subcall/src/main.rs new file mode 100644 index 0000000000..6b0442d842 --- /dev/null +++ b/smart-contracts/contracts/test/measure-gas-subcall/src/main.rs @@ -0,0 +1,94 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + contracts::Parameters, ApiError, CLType, CLValue, ContractHash, ContractVersion, EntryPoint, + EntryPointAccess, EntryPointType, EntryPoints, Phase, RuntimeArgs, +}; + +const ARG_TARGET: &str = "target_contract"; +const NOOP_EXT: &str = "noop_ext"; +const GET_PHASE_EXT: &str = "get_phase_ext"; + +#[repr(u16)] +enum CustomError { + UnexpectedPhaseInline = 0, + UnexpectedPhaseSub = 1, +} + +#[no_mangle] +pub extern "C" fn get_phase_ext() { + let phase = runtime::get_phase(); + runtime::ret(CLValue::from_t(phase).unwrap_or_revert()) +} + +#[no_mangle] +pub extern "C" fn noop_ext() { + runtime::ret(CLValue::from_t(()).unwrap_or_revert()) +} + +fn store() -> (ContractHash, ContractVersion) { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point_1 = EntryPoint::new( + NOOP_EXT, + Parameters::default(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point_1); + + let entry_point_2 = EntryPoint::new( + GET_PHASE_EXT, + Parameters::default(), + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + + entry_points.add_entry_point(entry_point_2); + + entry_points + }; + storage::new_contract(entry_points, None, None, None) +} + +#[no_mangle] +pub extern "C" fn call() { + const NOOP_EXT: &str = "noop_ext"; + const GET_PHASE_EXT: &str = "get_phase_ext"; + + let method_name: String = runtime::get_named_arg(ARG_TARGET); + match method_name.as_str() { + "no-subcall" => { + let phase = runtime::get_phase(); + if phase != Phase::Session { + runtime::revert(ApiError::User(CustomError::UnexpectedPhaseInline as u16)) + } + } + "do-nothing" => { + let (reference, _contract_version) = store(); + runtime::call_contract(reference, NOOP_EXT, RuntimeArgs::default()) + } + "do-something" => { + let (reference, _contract_version) = store(); + let phase: Phase = + runtime::call_contract(reference, GET_PHASE_EXT, RuntimeArgs::default()); + if phase != Phase::Session { + runtime::revert(ApiError::User(CustomError::UnexpectedPhaseSub as u16)) + } + } + _ => {} + } +} diff --git a/smart-contracts/contracts/test/mint-purse/Cargo.toml b/smart-contracts/contracts/test/mint-purse/Cargo.toml new file mode 100644 index 0000000000..14147b503c --- /dev/null +++ b/smart-contracts/contracts/test/mint-purse/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mint-purse" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "mint_purse" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/mint-purse/src/main.rs b/smart-contracts/contracts/test/mint-purse/src/main.rs new file mode 100644 index 0000000000..68c72678d3 --- /dev/null +++ b/smart-contracts/contracts/test/mint-purse/src/main.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{runtime_args, system_contract_errors::mint, ApiError, RuntimeArgs, URef, U512}; + +const METHOD_MINT: &str = "mint"; +const METHOD_BALANCE: &str = "balance"; + +const ARG_AMOUNT: &str = "amount"; +const ARG_PURSE: &str = "purse"; + +#[repr(u16)] +enum Error { + BalanceNotFound = 0, + BalanceMismatch, +} + +fn mint_purse(amount: U512) -> Result { + runtime::call_contract( + system::get_mint(), + METHOD_MINT, + runtime_args! { + ARG_AMOUNT => amount, + }, + ) +} + +#[no_mangle] +pub extern "C" fn call() { + let amount: U512 = 12345.into(); + let new_purse = mint_purse(amount).unwrap_or_revert(); + + let mint = system::get_mint(); + + let balance: Option = runtime::call_contract( + mint, + METHOD_BALANCE, + runtime_args! { + ARG_PURSE => new_purse, + }, + ); + + match balance { + None => runtime::revert(ApiError::User(Error::BalanceNotFound as u16)), + Some(balance) if balance == amount => (), + _ => runtime::revert(ApiError::User(Error::BalanceMismatch as u16)), + } +} diff --git a/smart-contracts/contracts/test/modified-mint-caller/Cargo.toml b/smart-contracts/contracts/test/modified-mint-caller/Cargo.toml new file mode 100644 index 0000000000..d7a35426c4 --- /dev/null +++ b/smart-contracts/contracts/test/modified-mint-caller/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "modified-mint-caller" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "modified_mint_caller" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/modified-mint-caller/src/main.rs b/smart-contracts/contracts/test/modified-mint-caller/src/main.rs new file mode 100644 index 0000000000..db667d7bbe --- /dev/null +++ b/smart-contracts/contracts/test/modified-mint-caller/src/main.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::contract_api::{runtime, storage, system}; +use types::{Key, RuntimeArgs}; + +const NEW_ENDPOINT_NAME: &str = "version"; +const RESULT_UREF_NAME: &str = "output_version"; + +#[no_mangle] +pub extern "C" fn call() { + let contract_hash = system::get_mint(); + let value: String = + runtime::call_contract(contract_hash, NEW_ENDPOINT_NAME, RuntimeArgs::default()); + runtime::put_key(RESULT_UREF_NAME, Key::URef(storage::new_uref(value))); +} diff --git a/smart-contracts/contracts/test/modified-mint-upgrader/Cargo.toml b/smart-contracts/contracts/test/modified-mint-upgrader/Cargo.toml new file mode 100644 index 0000000000..13e566906f --- /dev/null +++ b/smart-contracts/contracts/test/modified-mint-upgrader/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "modified-mint-upgrader" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "modified_mint_upgrader" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } +modified-mint = { path = "../modified-mint" } diff --git a/smart-contracts/contracts/test/modified-mint-upgrader/src/main.rs b/smart-contracts/contracts/test/modified-mint-upgrader/src/main.rs new file mode 100644 index 0000000000..05335b263e --- /dev/null +++ b/smart-contracts/contracts/test/modified-mint-upgrader/src/main.rs @@ -0,0 +1,66 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::collections::BTreeMap; +use contract::{ + contract_api::{runtime, storage, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{contracts::NamedKeys, CLValue, ContractHash, ContractVersion, URef}; + +pub const MODIFIED_MINT_EXT_FUNCTION_NAME: &str = "modified_mint_ext"; +pub const POS_EXT_FUNCTION_NAME: &str = "pos_ext"; +pub const STANDARD_PAYMENT_FUNCTION_NAME: &str = "pay"; + +#[no_mangle] +pub extern "C" fn mint() { + modified_mint::mint(); +} + +#[no_mangle] +pub extern "C" fn create() { + modified_mint::create(); +} + +#[no_mangle] +pub extern "C" fn balance() { + modified_mint::balance(); +} + +#[no_mangle] +pub extern "C" fn transfer() { + modified_mint::transfer(); +} + +fn upgrade_mint() -> (ContractHash, ContractVersion) { + const HASH_KEY_NAME: &str = "mint_hash"; + const ACCESS_KEY_NAME: &str = "mint_access"; + + let mint_package_hash: ContractHash = runtime::get_key(HASH_KEY_NAME) + .expect("should have mint") + .into_hash() + .expect("should be hash"); + let _mint_access_key: URef = runtime::get_key(ACCESS_KEY_NAME) + .unwrap_or_revert() + .into_uref() + .expect("should be uref"); + + let entry_points = modified_mint::get_entry_points(); + let named_keys = NamedKeys::new(); + storage::add_contract_version(mint_package_hash, entry_points, named_keys) +} + +#[no_mangle] +pub extern "C" fn upgrade() { + let mut upgrades: BTreeMap = BTreeMap::new(); + + { + let old_mint_hash = system::get_mint(); + let (new_mint_hash, _contract_version) = upgrade_mint(); + upgrades.insert(old_mint_hash, new_mint_hash); + } + + runtime::ret(CLValue::from_t(upgrades).unwrap()); +} diff --git a/smart-contracts/contracts/test/modified-mint/Cargo.toml b/smart-contracts/contracts/test/modified-mint/Cargo.toml new file mode 100644 index 0000000000..d8093c045a --- /dev/null +++ b/smart-contracts/contracts/test/modified-mint/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "modified-mint" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "modified_mint" +path = "src/bin/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +mint-token = { path = "../../system/mint-token" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/modified-mint/src/bin/main.rs b/smart-contracts/contracts/test/modified-mint/src/bin/main.rs new file mode 100644 index 0000000000..781e4f1a59 --- /dev/null +++ b/smart-contracts/contracts/test/modified-mint/src/bin/main.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +#[no_mangle] +pub extern "C" fn mint() { + modified_mint::mint(); +} + +#[no_mangle] +pub extern "C" fn create() { + modified_mint::create(); +} + +#[no_mangle] +pub extern "C" fn balance() { + modified_mint::balance(); +} + +#[no_mangle] +pub extern "C" fn transfer() { + modified_mint::transfer(); +} diff --git a/smart-contracts/contracts/test/modified-mint/src/lib.rs b/smart-contracts/contracts/test/modified-mint/src/lib.rs new file mode 100644 index 0000000000..ed5e5a8401 --- /dev/null +++ b/smart-contracts/contracts/test/modified-mint/src/lib.rs @@ -0,0 +1,160 @@ +#![no_std] + +#[macro_use] +extern crate alloc; + +use alloc::boxed::Box; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::mint::{Mint, RuntimeProvider, StorageProvider}; +use types::{ + account::AccountHash, + bytesrepr::{FromBytes, ToBytes}, + contracts::Parameters, + system_contract_errors::mint::Error, + CLType, CLTyped, CLValue, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Key, + Parameter, URef, U512, +}; + +pub const METHOD_MINT: &str = "mint"; +pub const METHOD_CREATE: &str = "create"; +pub const METHOD_BALANCE: &str = "balance"; +pub const METHOD_TRANSFER: &str = "transfer"; + +pub const ARG_AMOUNT: &str = "amount"; +pub const ARG_PURSE: &str = "purse"; +pub const ARG_SOURCE: &str = "source"; +pub const ARG_TARGET: &str = "target"; + +pub struct MintContract; + +impl RuntimeProvider for MintContract { + fn get_caller(&self) -> AccountHash { + runtime::get_caller() + } + + fn put_key(&mut self, name: &str, key: Key) { + runtime::put_key(name, key) + } +} + +impl StorageProvider for MintContract { + fn new_uref(&mut self, init: T) -> URef { + storage::new_uref(init) + } + + fn write_local(&mut self, key: K, value: V) { + storage::write_local(key, value) + } + + fn read_local( + &mut self, + key: &K, + ) -> Result, Error> { + storage::read_local(key).map_err(|_| Error::Storage) + } + + fn read(&mut self, uref: URef) -> Result, Error> { + storage::read(uref).map_err(|_| Error::Storage) + } + + fn write(&mut self, uref: URef, value: T) -> Result<(), Error> { + storage::write(uref, value); + Ok(()) + } + + fn add(&mut self, uref: URef, value: T) -> Result<(), Error> { + storage::add(uref, value); + Ok(()) + } +} + +impl Mint for MintContract {} + +pub fn mint() { + let mut mint_contract = MintContract; + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + let result: Result = mint_contract.mint(amount); + let ret = CLValue::from_t(result).unwrap_or_revert(); + runtime::ret(ret) +} + +pub fn create() { + let mut mint_contract = MintContract; + let uref = mint_contract.mint(U512::zero()).unwrap_or_revert(); + let ret = CLValue::from_t(uref).unwrap_or_revert(); + runtime::ret(ret) +} + +pub fn balance() { + let mut mint_contract = MintContract; + let uref: URef = runtime::get_named_arg(ARG_PURSE); + let balance: Option = mint_contract.balance(uref).unwrap_or_revert(); + let ret = CLValue::from_t(balance).unwrap_or_revert(); + runtime::ret(ret) +} + +pub fn transfer() { + let mut mint_contract = MintContract; + let source: URef = runtime::get_named_arg(ARG_SOURCE); + let target: URef = runtime::get_named_arg(ARG_TARGET); + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + let result: Result<(), Error> = mint_contract.transfer(source, target, amount); + let ret = CLValue::from_t(result).unwrap_or_revert(); + runtime::ret(ret); +} + +pub fn get_entry_points() -> EntryPoints { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + METHOD_MINT, + vec![Parameter::new(ARG_AMOUNT, CLType::U512)], + CLType::Result { + ok: Box::new(CLType::URef), + err: Box::new(CLType::U8), + }, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + + let entry_point = EntryPoint::new( + METHOD_CREATE, + Parameters::new(), + CLType::URef, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + + let entry_point = EntryPoint::new( + METHOD_BALANCE, + vec![Parameter::new(ARG_PURSE, CLType::URef)], + CLType::Option(Box::new(CLType::U512)), + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + + let entry_point = EntryPoint::new( + METHOD_TRANSFER, + vec![ + Parameter::new(ARG_SOURCE, CLType::URef), + Parameter::new(ARG_TARGET, CLType::URef), + Parameter::new(ARG_AMOUNT, CLType::U512), + ], + CLType::Result { + ok: Box::new(CLType::Unit), + err: Box::new(CLType::U8), + }, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + + entry_points +} diff --git a/smart-contracts/contracts/test/modified-system-upgrader/Cargo.toml b/smart-contracts/contracts/test/modified-system-upgrader/Cargo.toml new file mode 100644 index 0000000000..b1ef5b38aa --- /dev/null +++ b/smart-contracts/contracts/test/modified-system-upgrader/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "modified-system-upgrader" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "modified_system_upgrader" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +modified-mint = { path = "../modified-mint" } +pos = { path = "../../system/pos" } +standard-payment = { path = "../../system/standard-payment" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/modified-system-upgrader/src/main.rs b/smart-contracts/contracts/test/modified-system-upgrader/src/main.rs new file mode 100644 index 0000000000..62a155124c --- /dev/null +++ b/smart-contracts/contracts/test/modified-system-upgrader/src/main.rs @@ -0,0 +1,262 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate alloc; + +use alloc::boxed::Box; + +use alloc::collections::BTreeMap; +use contract::{ + contract_api::{runtime, storage, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + contracts::{NamedKeys, Parameters}, + CLType, CLValue, ContractHash, ContractVersion, EntryPoint, EntryPointAccess, EntryPointType, + EntryPoints, Parameter, URef, +}; + +pub const MODIFIED_MINT_EXT_FUNCTION_NAME: &str = "modified_mint_ext"; +pub const POS_EXT_FUNCTION_NAME: &str = "pos_ext"; +pub const STANDARD_PAYMENT_FUNCTION_NAME: &str = "pay"; +const VERSION_ENTRY_POINT: &str = "version"; +const UPGRADED_VERSION: &str = "1.1.0"; + +#[no_mangle] +pub extern "C" fn mint() { + modified_mint::mint(); +} + +#[no_mangle] +pub extern "C" fn create() { + modified_mint::create(); +} + +#[no_mangle] +pub extern "C" fn balance() { + modified_mint::balance(); +} + +#[no_mangle] +pub extern "C" fn transfer() { + modified_mint::transfer(); +} + +#[no_mangle] +pub extern "C" fn version() { + runtime::ret(CLValue::from_t(UPGRADED_VERSION).unwrap_or_revert()); +} + +#[no_mangle] +pub extern "C" fn call() { + standard_payment::delegate(); +} + +fn upgrade_mint() -> (ContractHash, ContractVersion) { + const HASH_KEY_NAME: &str = "mint_hash"; + const ACCESS_KEY_NAME: &str = "mint_access"; + + let mint_package_hash: ContractHash = runtime::get_key(HASH_KEY_NAME) + .expect("should have mint") + .into_hash() + .expect("should be hash"); + let _mint_access_key: URef = runtime::get_key(ACCESS_KEY_NAME) + .unwrap_or_revert() + .into_uref() + .expect("shuold be uref"); + + let mut entry_points = modified_mint::get_entry_points(); + let entry_point = EntryPoint::new( + VERSION_ENTRY_POINT, + Parameters::new(), + CLType::String, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(entry_point); + + let named_keys = NamedKeys::new(); + storage::add_contract_version(mint_package_hash, entry_points, named_keys) +} + +fn upgrade_proof_of_stake() -> (ContractHash, ContractVersion) { + use pos::{ + ARG_ACCOUNT_KEY, ARG_AMOUNT, ARG_PURSE, METHOD_BOND, METHOD_FINALIZE_PAYMENT, + METHOD_GET_PAYMENT_PURSE, METHOD_GET_REFUND_PURSE, METHOD_SET_REFUND_PURSE, METHOD_UNBOND, + }; + + const HASH_KEY_NAME: &str = "pos_hash"; + const ACCESS_KEY_NAME: &str = "pos_access"; + + let pos_package_hash: ContractHash = runtime::get_key(HASH_KEY_NAME) + .expect("should have mint") + .into_hash() + .expect("should be hash"); + let _pos_access_key: URef = runtime::get_key(ACCESS_KEY_NAME) + .unwrap_or_revert() + .into_uref() + .expect("should be uref"); + + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let bond = EntryPoint::new( + METHOD_BOND, + vec![ + Parameter::new(ARG_AMOUNT, CLType::U512), + Parameter::new(ARG_PURSE, CLType::URef), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(bond); + + let unbond = EntryPoint::new( + METHOD_UNBOND, + vec![Parameter::new(ARG_AMOUNT, CLType::U512)], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(unbond); + + let get_payment_purse = EntryPoint::new( + METHOD_GET_PAYMENT_PURSE, + vec![], + CLType::URef, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(get_payment_purse); + + let set_refund_purse = EntryPoint::new( + METHOD_SET_REFUND_PURSE, + vec![Parameter::new(ARG_PURSE, CLType::URef)], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(set_refund_purse); + + let get_refund_purse = EntryPoint::new( + METHOD_GET_REFUND_PURSE, + vec![], + CLType::Option(Box::new(CLType::URef)), + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(get_refund_purse); + + let finalize_payment = EntryPoint::new( + METHOD_FINALIZE_PAYMENT, + vec![ + Parameter::new(ARG_AMOUNT, CLType::U512), + Parameter::new(ARG_ACCOUNT_KEY, CLType::FixedList(Box::new(CLType::U8), 32)), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(finalize_payment); + + entry_points + }; + + let named_keys = NamedKeys::new(); + + storage::add_contract_version(pos_package_hash, entry_points, named_keys) +} + +#[no_mangle] +pub extern "C" fn bond() { + pos::bond(); +} + +#[no_mangle] +pub extern "C" fn unbond() { + pos::unbond(); +} + +#[no_mangle] +pub extern "C" fn get_payment_purse() { + pos::get_payment_purse(); +} + +#[no_mangle] +pub extern "C" fn set_refund_purse() { + pos::set_refund_purse(); +} + +#[no_mangle] +pub extern "C" fn get_refund_purse() { + pos::get_refund_purse(); +} + +#[no_mangle] +pub extern "C" fn finalize_payment() { + pos::finalize_payment(); +} + +fn upgrade_standard_payment() -> (ContractHash, ContractVersion) { + const HASH_KEY_NAME: &str = "standard_payment_hash"; + const ACCESS_KEY_NAME: &str = "standard_payment_access"; + const ARG_AMOUNT: &str = "amount"; + + let standard_payment_package_hash: ContractHash = runtime::get_key(HASH_KEY_NAME) + .expect("should have mint") + .into_hash() + .expect("should be hash"); + let _standard_payment_access_key: URef = runtime::get_key(ACCESS_KEY_NAME) + .unwrap_or_revert() + .into_uref() + .expect("shuold be uref"); + + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + "call", + vec![Parameter::new(ARG_AMOUNT, CLType::U512)], + CLType::Result { + ok: Box::new(CLType::Unit), + err: Box::new(CLType::U32), + }, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(entry_point); + + entry_points + }; + + let named_keys = NamedKeys::new(); + + storage::add_contract_version(standard_payment_package_hash, entry_points, named_keys) +} + +#[no_mangle] +pub extern "C" fn upgrade() { + let mut upgrades: BTreeMap = BTreeMap::new(); + + { + let old_mint_hash = system::get_mint(); + let (new_mint_hash, _new_mint_version) = upgrade_mint(); + upgrades.insert(old_mint_hash, new_mint_hash); + } + + { + let old_pos_hash = system::get_proof_of_stake(); + let (new_pos_hash, _new_pos_version) = upgrade_proof_of_stake(); + upgrades.insert(old_pos_hash, new_pos_hash); + } + + { + let old_standard_payment_hash = system::get_standard_payment(); + let (new_standard_payment_hash, _new_standard_payment_version) = upgrade_standard_payment(); + upgrades.insert(old_standard_payment_hash, new_standard_payment_hash); + } + + runtime::ret(CLValue::from_t(upgrades).unwrap()); +} diff --git a/smart-contracts/contracts/test/named-keys/Cargo.toml b/smart-contracts/contracts/test/named-keys/Cargo.toml new file mode 100644 index 0000000000..0d39f58951 --- /dev/null +++ b/smart-contracts/contracts/test/named-keys/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "named-keys" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "named_keys" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/named-keys/src/main.rs b/smart-contracts/contracts/test/named-keys/src/main.rs new file mode 100644 index 0000000000..56c4170e8d --- /dev/null +++ b/smart-contracts/contracts/test/named-keys/src/main.rs @@ -0,0 +1,90 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::{String, ToString}; +use core::convert::TryInto; + +use contract::{ + contract_api::{runtime, storage}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{bytesrepr::ToBytes, ApiError, CLTyped, Key, U512}; + +fn create_uref(key_name: &str, value: T) { + let key: Key = storage::new_uref(value).into(); + runtime::put_key(key_name, key); +} + +const COMMAND_CREATE_UREF1: &str = "create-uref1"; +const COMMAND_CREATE_UREF2: &str = "create-uref2"; +const COMMAND_REMOVE_UREF1: &str = "remove-uref1"; +const COMMAND_REMOVE_UREF2: &str = "remove-uref2"; +const COMMAND_TEST_READ_UREF1: &str = "test-read-uref1"; +const COMMAND_TEST_READ_UREF2: &str = "test-read-uref2"; +const COMMAND_INCREASE_UREF2: &str = "increase-uref2"; +const COMMAND_OVERWRITE_UREF2: &str = "overwrite-uref2"; +const ARG_COMMAND: &str = "command"; + +#[no_mangle] +pub extern "C" fn call() { + let command: String = runtime::get_named_arg(ARG_COMMAND); + + match command.as_str() { + COMMAND_CREATE_UREF1 => create_uref("hello-world", String::from("Hello, world!")), + COMMAND_CREATE_UREF2 => create_uref("big-value", U512::max_value()), + COMMAND_REMOVE_UREF1 => runtime::remove_key("hello-world"), + COMMAND_REMOVE_UREF2 => runtime::remove_key("big-value"), + COMMAND_TEST_READ_UREF1 => { + // Read data hidden behind `URef1` uref + let hello_world: String = storage::read( + runtime::list_named_keys() + .get("hello-world") + .expect("Unable to get hello-world") + .clone() + .try_into() + .expect("Unable to convert to uref"), + ) + .expect("Unable to deserialize URef") + .expect("Unable to find value"); + assert_eq!(hello_world, "Hello, world!"); + + // Read data through dedicated FFI function + let uref1 = runtime::get_key("hello-world").unwrap_or_revert(); + + let uref = uref1.try_into().unwrap_or_revert_with(ApiError::User(101)); + let hello_world = storage::read(uref); + assert_eq!(hello_world, Ok(Some("Hello, world!".to_string()))); + } + COMMAND_TEST_READ_UREF2 => { + // Get the big value back + let big_value_key = + runtime::get_key("big-value").unwrap_or_revert_with(ApiError::User(102)); + let big_value_ref = big_value_key.try_into().unwrap_or_revert(); + let big_value = storage::read(big_value_ref); + assert_eq!(big_value, Ok(Some(U512::max_value()))); + } + COMMAND_INCREASE_UREF2 => { + // Get the big value back + let big_value_key = + runtime::get_key("big-value").unwrap_or_revert_with(ApiError::User(102)); + let big_value_ref = big_value_key.try_into().unwrap_or_revert(); + // Increase by 1 + storage::add(big_value_ref, U512::one()); + let new_big_value = storage::read(big_value_ref); + assert_eq!(new_big_value, Ok(Some(U512::zero()))); + } + COMMAND_OVERWRITE_UREF2 => { + // Get the big value back + let big_value_key = + runtime::get_key("big-value").unwrap_or_revert_with(ApiError::User(102)); + let big_value_ref = big_value_key.try_into().unwrap_or_revert(); + // I can overwrite some data under the pointer + storage::write(big_value_ref, U512::from(123_456_789u64)); + let new_value = storage::read(big_value_ref); + assert_eq!(new_value, Ok(Some(U512::from(123_456_789u64)))); + } + _ => runtime::revert(ApiError::InvalidArgument), + } +} diff --git a/smart-contracts/contracts/test/overwrite-uref-content/Cargo.toml b/smart-contracts/contracts/test/overwrite-uref-content/Cargo.toml new file mode 100644 index 0000000000..a82577eab2 --- /dev/null +++ b/smart-contracts/contracts/test/overwrite-uref-content/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "overwrite-uref-content" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "overwrite_uref_content" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/overwrite-uref-content/src/main.rs b/smart-contracts/contracts/test/overwrite-uref-content/src/main.rs new file mode 100644 index 0000000000..abed2b3bbe --- /dev/null +++ b/smart-contracts/contracts/test/overwrite-uref-content/src/main.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] + +use contract::contract_api::{runtime, storage}; +use types::{AccessRights, ApiError, URef}; + +const ARG_CONTRACT_UREF: &str = "contract_uref"; + +#[repr(u16)] +enum Error { + InvalidURefArg, +} + +const REPLACEMENT_DATA: &str = "bawitdaba"; + +#[no_mangle] +pub extern "C" fn call() { + let uref: URef = runtime::get_named_arg(ARG_CONTRACT_UREF); + + let is_valid = runtime::is_valid_uref(uref); + if !is_valid { + runtime::revert(ApiError::User(Error::InvalidURefArg as u16)) + } + + let forged_reference: URef = URef::new(uref.addr(), AccessRights::READ_ADD_WRITE); + + storage::write(forged_reference, REPLACEMENT_DATA) +} diff --git a/smart-contracts/contracts/test/pos-bonding/Cargo.toml b/smart-contracts/contracts/test/pos-bonding/Cargo.toml new file mode 100644 index 0000000000..a52545201f --- /dev/null +++ b/smart-contracts/contracts/test/pos-bonding/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "pos-bonding" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "pos_bonding" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/pos-bonding/src/main.rs b/smart-contracts/contracts/test/pos-bonding/src/main.rs new file mode 100644 index 0000000000..8a8c9e7de9 --- /dev/null +++ b/smart-contracts/contracts/test/pos-bonding/src/main.rs @@ -0,0 +1,88 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; + +use types::{account::AccountHash, runtime_args, ApiError, ContractHash, RuntimeArgs, URef, U512}; + +const ARG_AMOUNT: &str = "amount"; +const ARG_PURSE: &str = "purse"; +const ARG_ENTRY_POINT: &str = "entry_point"; +const ARG_BOND: &str = "bond"; +const ARG_UNBOND: &str = "unbond"; +const ARG_ACCOUNT_HASH: &str = "account_hash"; +const TEST_BOND_FROM_MAIN_PURSE: &str = "bond-from-main-purse"; +const TEST_SEED_NEW_ACCOUNT: &str = "seed_new_account"; + +#[repr(u16)] +enum Error { + UnableToSeedAccount, + UnknownCommand, +} + +#[no_mangle] +pub extern "C" fn call() { + let command: String = runtime::get_named_arg(ARG_ENTRY_POINT); + + match command.as_str() { + ARG_BOND => bond(), + ARG_UNBOND => unbond(), + TEST_BOND_FROM_MAIN_PURSE => bond_from_main_purse(), + TEST_SEED_NEW_ACCOUNT => seed_new_account(), + _ => runtime::revert(ApiError::User(Error::UnknownCommand as u16)), + } +} + +fn bond() { + let pos_contract_hash = system::get_proof_of_stake(); + // Creates new purse with desired amount based on main purse and sends funds + let amount = runtime::get_named_arg(ARG_AMOUNT); + let bonding_purse = system::create_purse(); + + system::transfer_from_purse_to_purse(account::get_main_purse(), bonding_purse, amount) + .unwrap_or_revert(); + + bonding(pos_contract_hash, amount, bonding_purse); +} + +fn bond_from_main_purse() { + let pos_contract_hash = system::get_proof_of_stake(); + let amount = runtime::get_named_arg(ARG_AMOUNT); + bonding(pos_contract_hash, amount, account::get_main_purse()); +} + +fn bonding(pos: ContractHash, bond_amount: U512, bonding_purse: URef) { + let args = runtime_args! { + ARG_AMOUNT => bond_amount, + ARG_PURSE => bonding_purse, + }; + runtime::call_contract(pos, ARG_BOND, args) +} + +fn unbond() { + let pos_contract_hash = system::get_proof_of_stake(); + let maybe_amount: Option = runtime::get_named_arg(ARG_AMOUNT); + unbonding(pos_contract_hash, maybe_amount); +} + +fn unbonding(pos: ContractHash, unbond_amount: Option) { + let args = runtime_args! { + ARG_AMOUNT => unbond_amount, + }; + runtime::call_contract(pos, ARG_UNBOND, args) +} + +fn seed_new_account() { + let source = account::get_main_purse(); + let target: AccountHash = runtime::get_named_arg(ARG_ACCOUNT_HASH); + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + system::transfer_from_purse_to_account(source, target, amount) + .unwrap_or_revert_with(ApiError::User(Error::UnableToSeedAccount as u16)); +} diff --git a/smart-contracts/contracts/test/pos-finalize-payment/Cargo.toml b/smart-contracts/contracts/test/pos-finalize-payment/Cargo.toml new file mode 100644 index 0000000000..984a3292f0 --- /dev/null +++ b/smart-contracts/contracts/test/pos-finalize-payment/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "pos-finalize-payment" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "pos_finalize_payment" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/pos-finalize-payment/src/main.rs b/smart-contracts/contracts/test/pos-finalize-payment/src/main.rs new file mode 100644 index 0000000000..fe890b39b6 --- /dev/null +++ b/smart-contracts/contracts/test/pos-finalize-payment/src/main.rs @@ -0,0 +1,67 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{account::AccountHash, runtime_args, ContractHash, RuntimeArgs, URef, U512}; + +pub const ARG_AMOUNT: &str = "amount"; +pub const ARG_AMOUNT_SPENT: &str = "amount_spent"; +pub const ARG_REFUND_FLAG: &str = "refund"; +pub const ARG_PURSE: &str = "purse"; +pub const ARG_ACCOUNT_KEY: &str = "account"; + +fn set_refund_purse(contract_hash: ContractHash, purse: URef) { + runtime::call_contract( + contract_hash, + "set_refund_purse", + runtime_args! { + ARG_PURSE => purse, + }, + ) +} + +fn get_payment_purse(contract_hash: ContractHash) -> URef { + runtime::call_contract(contract_hash, "get_payment_purse", runtime_args! {}) +} + +fn submit_payment(contract_hash: ContractHash, amount: U512) { + let payment_purse = get_payment_purse(contract_hash); + let main_purse = account::get_main_purse(); + system::transfer_from_purse_to_purse(main_purse, payment_purse, amount).unwrap_or_revert() +} + +fn finalize_payment(contract_hash: ContractHash, amount_spent: U512, account: AccountHash) { + runtime::call_contract( + contract_hash, + "finalize_payment", + runtime_args! { + ARG_AMOUNT => amount_spent, + ARG_ACCOUNT_KEY => account, + }, + ) +} + +#[no_mangle] +pub extern "C" fn call() { + let contract_hash = system::get_proof_of_stake(); + + let payment_amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + let refund_purse_flag: u8 = runtime::get_named_arg(ARG_REFUND_FLAG); + let maybe_amount_spent: Option = runtime::get_named_arg(ARG_AMOUNT_SPENT); + let maybe_account: Option = runtime::get_named_arg(ARG_ACCOUNT_KEY); + + submit_payment(contract_hash, payment_amount); + + if refund_purse_flag != 0 { + let refund_purse = system::create_purse(); + runtime::put_key("local_refund_purse", refund_purse.into()); + set_refund_purse(contract_hash, refund_purse); + } + + if let (Some(amount_spent), Some(account)) = (maybe_amount_spent, maybe_account) { + finalize_payment(contract_hash, amount_spent, account); + } +} diff --git a/smart-contracts/contracts/test/pos-get-payment-purse/Cargo.toml b/smart-contracts/contracts/test/pos-get-payment-purse/Cargo.toml new file mode 100644 index 0000000000..97bd2e3a56 --- /dev/null +++ b/smart-contracts/contracts/test/pos-get-payment-purse/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "pos-get-payment-purse" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "pos_get_payment_purse" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/pos-get-payment-purse/src/main.rs b/smart-contracts/contracts/test/pos-get-payment-purse/src/main.rs new file mode 100644 index 0000000000..e8b83adc28 --- /dev/null +++ b/smart-contracts/contracts/test/pos-get-payment-purse/src/main.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ApiError, RuntimeArgs, URef, U512}; + +#[repr(u16)] +enum Error { + TransferFromSourceToPayment = 0, + TransferFromPaymentToSource, + GetBalance, + CheckBalance, +} + +const ARG_AMOUNT: &str = "amount"; +const ENTRY_POINT_GET_PAYMENT_PURSE: &str = "get_payment_purse"; + +#[no_mangle] +pub extern "C" fn call() { + // amount passed to payment contract + let payment_fund: U512 = runtime::get_named_arg(ARG_AMOUNT); + + let contract_hash = system::get_proof_of_stake(); + let source_purse = account::get_main_purse(); + let payment_amount: U512 = 100.into(); + let payment_purse: URef = runtime::call_contract( + contract_hash, + ENTRY_POINT_GET_PAYMENT_PURSE, + RuntimeArgs::default(), + ); + + // can deposit + system::transfer_from_purse_to_purse(source_purse, payment_purse, payment_amount) + .unwrap_or_revert_with(ApiError::User(Error::TransferFromSourceToPayment as u16)); + + let payment_balance = system::get_balance(payment_purse) + .unwrap_or_revert_with(ApiError::User(Error::GetBalance as u16)); + + if payment_balance.saturating_sub(payment_fund) != payment_amount { + runtime::revert(ApiError::User(Error::CheckBalance as u16)) + } + + // cannot withdraw + if system::transfer_from_purse_to_purse(payment_purse, source_purse, payment_amount).is_ok() { + runtime::revert(ApiError::User(Error::TransferFromPaymentToSource as u16)); + } +} diff --git a/smart-contracts/contracts/test/pos-refund-purse/Cargo.toml b/smart-contracts/contracts/test/pos-refund-purse/Cargo.toml new file mode 100644 index 0000000000..4cad6e06b5 --- /dev/null +++ b/smart-contracts/contracts/test/pos-refund-purse/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "pos-refund-purse" +version = "0.1.0" +authors = ["Michael Birch "] +edition = "2018" + +[[bin]] +name = "pos_refund_purse" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/pos-refund-purse/src/main.rs b/smart-contracts/contracts/test/pos-refund-purse/src/main.rs new file mode 100644 index 0000000000..291adce2d6 --- /dev/null +++ b/smart-contracts/contracts/test/pos-refund-purse/src/main.rs @@ -0,0 +1,86 @@ +#![no_std] +#![no_main] + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{runtime_args, ApiError, ContractHash, RuntimeArgs, URef, U512}; + +#[repr(u16)] +enum Error { + ShouldNotExist = 0, + NotFound, + Invalid, + IncorrectAccessRights, +} + +pub const ARG_PURSE: &str = "purse"; +const ARG_PAYMENT_AMOUNT: &str = "payment_amount"; +const SET_REFUND_PURSE: &str = "set_refund_purse"; +const GET_REFUND_PURSE: &str = "get_refund_purse"; +const GET_PAYMENT_PURSE: &str = "get_payment_purse"; + +fn set_refund_purse(contract_hash: ContractHash, p: &URef) { + runtime::call_contract( + contract_hash, + SET_REFUND_PURSE, + runtime_args! { + ARG_PURSE => *p, + }, + ) +} + +fn get_refund_purse(pos: ContractHash) -> Option { + runtime::call_contract(pos, GET_REFUND_PURSE, runtime_args! {}) +} + +fn get_payment_purse(pos: ContractHash) -> URef { + runtime::call_contract(pos, GET_PAYMENT_PURSE, runtime_args! {}) +} + +fn submit_payment(pos: ContractHash, amount: U512) { + let payment_purse = get_payment_purse(pos); + let main_purse = account::get_main_purse(); + system::transfer_from_purse_to_purse(main_purse, payment_purse, amount).unwrap_or_revert() +} + +#[no_mangle] +pub extern "C" fn call() { + let pos = system::get_proof_of_stake(); + + let refund_purse = system::create_purse(); + { + // get_refund_purse should return None before setting it + let refund_result = get_refund_purse(pos); + if refund_result.is_some() { + runtime::revert(ApiError::User(Error::ShouldNotExist as u16)); + } + + // it should return Some(x) after calling set_refund_purse(x) + set_refund_purse(pos, &refund_purse); + let refund_purse = match get_refund_purse(pos) { + None => runtime::revert(ApiError::User(Error::NotFound as u16)), + Some(x) if x.addr() == refund_purse.addr() => x, + Some(_) => runtime::revert(ApiError::User(Error::Invalid as u16)), + }; + + // the returned purse should not have any access rights + if refund_purse.is_addable() || refund_purse.is_writeable() || refund_purse.is_readable() { + runtime::revert(ApiError::User(Error::IncorrectAccessRights as u16)) + } + } + { + let refund_purse = system::create_purse(); + // get_refund_purse should return correct value after setting a second time + set_refund_purse(pos, &refund_purse); + match get_refund_purse(pos) { + None => runtime::revert(ApiError::User(Error::NotFound as u16)), + Some(uref) if uref.addr() == refund_purse.addr() => (), + Some(_) => runtime::revert(ApiError::User(Error::Invalid as u16)), + } + + let payment_amount: U512 = runtime::get_named_arg(ARG_PAYMENT_AMOUNT); + submit_payment(pos, payment_amount); + } +} diff --git a/smart-contracts/contracts/test/purse-holder-stored-caller/Cargo.toml b/smart-contracts/contracts/test/purse-holder-stored-caller/Cargo.toml new file mode 100644 index 0000000000..8ca0ae1a24 --- /dev/null +++ b/smart-contracts/contracts/test/purse-holder-stored-caller/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "purse-holder-stored-caller" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "purse_holder_stored_caller" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/purse-holder-stored-caller/src/main.rs b/smart-contracts/contracts/test/purse-holder-stored-caller/src/main.rs new file mode 100644 index 0000000000..42489adbc6 --- /dev/null +++ b/smart-contracts/contracts/test/purse-holder-stored-caller/src/main.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::contract_api::{runtime, storage}; +use types::{runtime_args, ContractHash, RuntimeArgs}; + +const METHOD_VERSION: &str = "version"; +const HASH_KEY_NAME: &str = "purse_holder"; +const ENTRY_POINT_NAME: &str = "entry_point"; +const PURSE_NAME: &str = "purse_name"; + +#[no_mangle] +pub extern "C" fn call() { + let entry_point_name: String = runtime::get_named_arg(ENTRY_POINT_NAME); + + match entry_point_name.as_str() { + METHOD_VERSION => { + let contract_hash: ContractHash = runtime::get_named_arg(HASH_KEY_NAME); + let version: String = + runtime::call_contract(contract_hash, &entry_point_name, RuntimeArgs::default()); + let version_key = storage::new_uref(version).into(); + runtime::put_key(METHOD_VERSION, version_key); + } + _ => { + let contract_hash: ContractHash = runtime::get_named_arg(HASH_KEY_NAME); + let purse_name: String = runtime::get_named_arg(PURSE_NAME); + + let args = runtime_args! { + PURSE_NAME => purse_name, + }; + runtime::call_contract::<()>(contract_hash, &entry_point_name, args); + } + }; +} diff --git a/smart-contracts/contracts/test/purse-holder-stored-upgrader/Cargo.toml b/smart-contracts/contracts/test/purse-holder-stored-upgrader/Cargo.toml new file mode 100644 index 0000000000..f7519d7093 --- /dev/null +++ b/smart-contracts/contracts/test/purse-holder-stored-upgrader/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "purse-holder-stored-upgrader" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "purse_holder_stored_upgrader" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/purse-holder-stored-upgrader/src/main.rs b/smart-contracts/contracts/test/purse-holder-stored-upgrader/src/main.rs new file mode 100644 index 0000000000..b0f2ec7754 --- /dev/null +++ b/smart-contracts/contracts/test/purse-holder-stored-upgrader/src/main.rs @@ -0,0 +1,99 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate alloc; + +use alloc::string::String; + +use contract::{ + contract_api::{runtime, storage, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + contracts::NamedKeys, CLType, CLValue, ContractPackageHash, EntryPoint, EntryPointAccess, + EntryPointType, EntryPoints, Parameter, URef, +}; + +pub const METHOD_ADD: &str = "add"; +pub const METHOD_REMOVE: &str = "remove"; +pub const METHOD_VERSION: &str = "version"; +pub const ARG_PURSE_NAME: &str = "purse_name"; +pub const NEW_VERSION: &str = "1.0.1"; +const VERSION: &str = "version"; +const ACCESS_KEY_NAME: &str = "purse_holder_access"; +const PURSE_HOLDER_STORED_CONTRACT_NAME: &str = "purse_holder_stored"; +const ARG_CONTRACT_PACKAGE: &str = "contract_package"; +const CONTRACT_VERSION: &str = "contract_version"; + +fn purse_name() -> String { + runtime::get_named_arg(ARG_PURSE_NAME) +} + +#[no_mangle] +pub extern "C" fn add() { + let purse_name = purse_name(); + let purse = system::create_purse(); + runtime::put_key(&purse_name, purse.into()); +} + +#[no_mangle] +pub extern "C" fn remove() { + let purse_name = purse_name(); + runtime::remove_key(&purse_name); +} + +#[no_mangle] +pub extern "C" fn version() { + runtime::ret(CLValue::from_t(VERSION).unwrap_or_revert()) +} + +#[no_mangle] +pub extern "C" fn call() { + let contract_package: ContractPackageHash = runtime::get_named_arg(ARG_CONTRACT_PACKAGE); + let _access_key: URef = runtime::get_key(ACCESS_KEY_NAME) + .expect("should have access key") + .into_uref() + .expect("should be uref"); + + let entry_points = { + let mut entry_points = EntryPoints::new(); + let add = EntryPoint::new( + METHOD_ADD, + vec![Parameter::new(ARG_PURSE_NAME, CLType::String)], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(add); + let version = EntryPoint::new( + METHOD_VERSION, + vec![], + CLType::String, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(version); + + let remove = EntryPoint::new( + METHOD_REMOVE, + vec![Parameter::new(ARG_PURSE_NAME, CLType::String)], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(remove); + entry_points + }; + // this should overwrite the previous contract obj with the new contract obj at the same uref + let (new_contract_hash, new_contract_version) = + storage::add_contract_version(contract_package, entry_points, NamedKeys::new()); + runtime::put_key(PURSE_HOLDER_STORED_CONTRACT_NAME, new_contract_hash.into()); + runtime::put_key( + CONTRACT_VERSION, + storage::new_uref(new_contract_version).into(), + ); + // set new version + let version_key = storage::new_uref(NEW_VERSION).into(); + runtime::put_key(VERSION, version_key); +} diff --git a/smart-contracts/contracts/test/purse-holder-stored/Cargo.toml b/smart-contracts/contracts/test/purse-holder-stored/Cargo.toml new file mode 100644 index 0000000000..92b93adff4 --- /dev/null +++ b/smart-contracts/contracts/test/purse-holder-stored/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "purse-holder-stored" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "purse_holder_stored" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/purse-holder-stored/src/main.rs b/smart-contracts/contracts/test/purse-holder-stored/src/main.rs new file mode 100644 index 0000000000..ba34fc99b0 --- /dev/null +++ b/smart-contracts/contracts/test/purse-holder-stored/src/main.rs @@ -0,0 +1,76 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{string::ToString, vec}; + +use alloc::string::String; +use contract::{ + self, + contract_api::{runtime, storage, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ + CLType, CLValue, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Parameter, +}; + +pub const METHOD_ADD: &str = "add"; +pub const METHOD_REMOVE: &str = "remove"; +pub const METHOD_VERSION: &str = "version"; + +const ENTRY_POINT_ADD: &str = "add_named_purse"; +const ENTRY_POINT_VERSION: &str = "version"; +const HASH_KEY_NAME: &str = "purse_holder"; +const ACCESS_KEY_NAME: &str = "purse_holder_access"; +const ARG_PURSE: &str = "purse_name"; +const VERSION: &str = "1.0.0"; +const PURSE_HOLDER_STORED_CONTRACT_NAME: &str = "purse_holder_stored"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[no_mangle] +pub extern "C" fn add_named_purse() { + let purse_name: String = runtime::get_named_arg(ARG_PURSE); + let purse = system::create_purse(); + runtime::put_key(&purse_name, purse.into()); +} + +#[no_mangle] +pub extern "C" fn version() { + let ret = CLValue::from_t(VERSION).unwrap_or_revert(); + runtime::ret(ret); +} + +#[no_mangle] +pub extern "C" fn call() { + let entry_points = { + let mut entry_points = EntryPoints::new(); + let add = EntryPoint::new( + ENTRY_POINT_ADD.to_string(), + vec![Parameter::new(ARG_PURSE, CLType::String)], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(add); + let version = EntryPoint::new( + ENTRY_POINT_VERSION.to_string(), + vec![], + CLType::String, + EntryPointAccess::Public, + EntryPointType::Contract, + ); + entry_points.add_entry_point(version); + entry_points + }; + + let (contract_hash, contract_version) = storage::new_contract( + entry_points, + None, + Some(HASH_KEY_NAME.to_string()), + Some(ACCESS_KEY_NAME.to_string()), + ); + runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into()); + runtime::put_key(PURSE_HOLDER_STORED_CONTRACT_NAME, contract_hash.into()); + runtime::put_key(ENTRY_POINT_VERSION, storage::new_uref(VERSION).into()); +} diff --git a/smart-contracts/contracts/test/remove-associated-key/Cargo.toml b/smart-contracts/contracts/test/remove-associated-key/Cargo.toml new file mode 100644 index 0000000000..a70800b8c7 --- /dev/null +++ b/smart-contracts/contracts/test/remove-associated-key/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "remove-associated-key" +version = "0.1.0" +authors = ["Ed Hastings ", "Ed Hastings "] +edition = "2018" + +[[bin]] +name = "test_payment_stored" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +standard-payment = { path = "../../system/standard-payment" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/test-payment-stored/src/main.rs b/smart-contracts/contracts/test/test-payment-stored/src/main.rs new file mode 100644 index 0000000000..0c6bce35cd --- /dev/null +++ b/smart-contracts/contracts/test/test-payment-stored/src/main.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{string::ToString, vec}; + +use contract::contract_api::{runtime, storage}; +use types::{ + contracts::{EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Parameter}, + CLType, +}; + +const ENTRY_FUNCTION_NAME: &str = "pay"; +const HASH_KEY_NAME: &str = "test_payment_hash"; +const PACKAGE_HASH_KEY_NAME: &str = "test_payment_package_hash"; +const ACCESS_KEY_NAME: &str = "test_payment_access"; +const ARG_NAME: &str = "amount"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[no_mangle] +pub extern "C" fn pay() { + standard_payment::delegate(); +} + +#[no_mangle] +pub extern "C" fn call() { + let entry_points = { + let mut entry_points = EntryPoints::new(); + let entry_point = EntryPoint::new( + ENTRY_FUNCTION_NAME.to_string(), + vec![Parameter::new(ARG_NAME, CLType::U512)], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(entry_point); + entry_points + }; + let (contract_hash, contract_version) = storage::new_contract( + entry_points, + None, + Some(PACKAGE_HASH_KEY_NAME.to_string()), + Some(ACCESS_KEY_NAME.to_string()), + ); + runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into()); + runtime::put_key(HASH_KEY_NAME, contract_hash.into()); +} diff --git a/smart-contracts/contracts/test/transfer-main-purse-to-new-purse/Cargo.toml b/smart-contracts/contracts/test/transfer-main-purse-to-new-purse/Cargo.toml new file mode 100644 index 0000000000..5f201eff84 --- /dev/null +++ b/smart-contracts/contracts/test/transfer-main-purse-to-new-purse/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "transfer-main-purse-to-new-purse" +version = "0.1.0" +authors = ["Ed Hastings "] +edition = "2018" + +[[bin]] +name = "transfer_main_purse_to_new_purse" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/transfer-main-purse-to-new-purse/src/main.rs b/smart-contracts/contracts/test/transfer-main-purse-to-new-purse/src/main.rs new file mode 100644 index 0000000000..5ee3e20581 --- /dev/null +++ b/smart-contracts/contracts/test/transfer-main-purse-to-new-purse/src/main.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{URef, U512}; + +const ARG_AMOUNT: &str = "amount"; +const ARG_DESTINATION: &str = "destination"; + +#[no_mangle] +pub extern "C" fn call() { + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + let destination_name: String = runtime::get_named_arg(ARG_DESTINATION); + + let source: URef = account::get_main_purse(); + let destination = system::create_purse(); + system::transfer_from_purse_to_purse(source, destination, amount).unwrap_or_revert(); + runtime::put_key(&destination_name, destination.into()); +} diff --git a/smart-contracts/contracts/test/transfer-main-purse-to-two-purses/Cargo.toml b/smart-contracts/contracts/test/transfer-main-purse-to-two-purses/Cargo.toml new file mode 100644 index 0000000000..31973b8b22 --- /dev/null +++ b/smart-contracts/contracts/test/transfer-main-purse-to-two-purses/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "transfer-main-purse-to-two-purses" +version = "0.1.0" +authors = ["Joe Sacher "] +edition = "2018" + +[[bin]] +name = "transfer_main_purse_to_two_purses" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/transfer-main-purse-to-two-purses/src/main.rs b/smart-contracts/contracts/test/transfer-main-purse-to-two-purses/src/main.rs new file mode 100644 index 0000000000..5567e3953c --- /dev/null +++ b/smart-contracts/contracts/test/transfer-main-purse-to-two-purses/src/main.rs @@ -0,0 +1,59 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::string::String; + +use contract::{ + contract_api::{account, runtime, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ApiError, URef, U512}; + +const DESTINATION_PURSE_ONE: &str = "destination_purse_one"; +const DESTINATION_PURSE_TWO: &str = "destination_purse_two"; +const TRANSFER_AMOUNT_ONE: &str = "transfer_amount_one"; +const TRANSFER_AMOUNT_TWO: &str = "transfer_amount_two"; + +#[repr(u16)] +enum CustomError { + TransferToPurseOneFailed = 101, + TransferToPurseTwoFailed = 102, +} + +fn get_or_create_purse(purse_name: &str) -> URef { + match runtime::get_key(purse_name) { + None => { + // Create and store purse if doesn't exist + let purse = system::create_purse(); + runtime::put_key(purse_name, purse.into()); + purse + } + Some(purse_key) => match purse_key.as_uref() { + Some(uref) => *uref, + None => runtime::revert(ApiError::UnexpectedKeyVariant), + }, + } +} + +#[no_mangle] +pub extern "C" fn call() { + let main_purse: URef = account::get_main_purse(); + + let destination_purse_one_name: String = runtime::get_named_arg(DESTINATION_PURSE_ONE); + + let destination_purse_one = get_or_create_purse(&destination_purse_one_name); + + let destination_purse_two_name: String = runtime::get_named_arg(DESTINATION_PURSE_TWO); + let transfer_amount_one: U512 = runtime::get_named_arg(TRANSFER_AMOUNT_ONE); + + let destination_purse_two = get_or_create_purse(&destination_purse_two_name); + + let transfer_amount_two: U512 = runtime::get_named_arg(TRANSFER_AMOUNT_TWO); + + system::transfer_from_purse_to_purse(main_purse, destination_purse_one, transfer_amount_one) + .unwrap_or_revert_with(ApiError::User(CustomError::TransferToPurseOneFailed as u16)); + system::transfer_from_purse_to_purse(main_purse, destination_purse_two, transfer_amount_two) + .unwrap_or_revert_with(ApiError::User(CustomError::TransferToPurseTwoFailed as u16)); +} diff --git a/smart-contracts/contracts/test/transfer-purse-to-account-stored/Cargo.toml b/smart-contracts/contracts/test/transfer-purse-to-account-stored/Cargo.toml new file mode 100644 index 0000000000..e39239035b --- /dev/null +++ b/smart-contracts/contracts/test/transfer-purse-to-account-stored/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "transfer-purse-to-account-stored" +version = "0.1.0" +authors = ["Michał Papierski ", "Ed Hastings "] +edition = "2018" + +[[bin]] +name = "transfer_purse_to_account_stored" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } +transfer_purse_to_account = { path = "../transfer-purse-to-account", package = "transfer-purse-to-account" } \ No newline at end of file diff --git a/smart-contracts/contracts/test/transfer-purse-to-account-stored/src/main.rs b/smart-contracts/contracts/test/transfer-purse-to-account-stored/src/main.rs new file mode 100644 index 0000000000..13ca67322f --- /dev/null +++ b/smart-contracts/contracts/test/transfer-purse-to-account-stored/src/main.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{string::ToString, vec}; + +use alloc::boxed::Box; +use contract::contract_api::{runtime, storage}; + +use types::{ + contracts::{EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Parameter}, + CLType, +}; + +const ENTRY_FUNCTION_NAME: &str = "transfer"; +const PACKAGE_HASH_KEY_NAME: &str = "transfer_purse_to_account"; +const HASH_KEY_NAME: &str = "transfer_purse_to_account_hash"; +const ACCESS_KEY_NAME: &str = "transfer_purse_to_account_access"; +const ARG_0_NAME: &str = "target_account_addr"; +const ARG_1_NAME: &str = "amount"; +const CONTRACT_VERSION: &str = "contract_version"; + +#[no_mangle] +pub extern "C" fn transfer() { + transfer_purse_to_account::delegate(); +} + +#[no_mangle] +pub extern "C" fn call() { + let entry_points = { + let mut entry_points = EntryPoints::new(); + + let entry_point = EntryPoint::new( + ENTRY_FUNCTION_NAME.to_string(), + vec![ + Parameter::new(ARG_0_NAME, CLType::FixedList(Box::new(CLType::U8), 32)), + Parameter::new(ARG_1_NAME, CLType::U512), + ], + CLType::Unit, + EntryPointAccess::Public, + EntryPointType::Session, + ); + entry_points.add_entry_point(entry_point); + entry_points + }; + + let (contract_hash, contract_version) = storage::new_contract( + entry_points, + None, + Some(PACKAGE_HASH_KEY_NAME.to_string()), + Some(ACCESS_KEY_NAME.to_string()), + ); + runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into()); + runtime::put_key(HASH_KEY_NAME, contract_hash.into()); +} diff --git a/smart-contracts/contracts/test/transfer-purse-to-account/Cargo.toml b/smart-contracts/contracts/test/transfer-purse-to-account/Cargo.toml new file mode 100644 index 0000000000..dd0133708c --- /dev/null +++ b/smart-contracts/contracts/test/transfer-purse-to-account/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "transfer-purse-to-account" +version = "0.1.0" +authors = ["Michał Papierski "] +edition = "2018" + +[[bin]] +name = "transfer_purse_to_account" +path = "src/bin/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/transfer-purse-to-account/src/bin/main.rs b/smart-contracts/contracts/test/transfer-purse-to-account/src/bin/main.rs new file mode 100644 index 0000000000..4d75717987 --- /dev/null +++ b/smart-contracts/contracts/test/transfer-purse-to-account/src/bin/main.rs @@ -0,0 +1,7 @@ +#![no_std] +#![no_main] + +#[no_mangle] +pub extern "C" fn call() { + transfer_purse_to_account::delegate(); +} diff --git a/smart-contracts/contracts/test/transfer-purse-to-account/src/lib.rs b/smart-contracts/contracts/test/transfer-purse-to-account/src/lib.rs new file mode 100644 index 0000000000..af978d8948 --- /dev/null +++ b/smart-contracts/contracts/test/transfer-purse-to-account/src/lib.rs @@ -0,0 +1,36 @@ +#![no_std] + +extern crate alloc; + +use alloc::format; + +use contract::{ + contract_api::{account, runtime, storage, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{account::AccountHash, ApiError, Key, URef, U512}; + +const TRANSFER_RESULT_UREF_NAME: &str = "transfer_result"; +const MAIN_PURSE_FINAL_BALANCE_UREF_NAME: &str = "final_balance"; + +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +pub fn delegate() { + let source: URef = account::get_main_purse(); + let target: AccountHash = runtime::get_named_arg(ARG_TARGET); + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + + let transfer_result = system::transfer_from_purse_to_account(source, target, amount); + + let final_balance = system::get_balance(source).unwrap_or_revert_with(ApiError::User(103)); + + let result = format!("{:?}", transfer_result); + + let result_uref: Key = storage::new_uref(result).into(); + runtime::put_key(TRANSFER_RESULT_UREF_NAME, result_uref); + runtime::put_key( + MAIN_PURSE_FINAL_BALANCE_UREF_NAME, + storage::new_uref(final_balance).into(), + ); +} diff --git a/smart-contracts/contracts/test/transfer-purse-to-purse/Cargo.toml b/smart-contracts/contracts/test/transfer-purse-to-purse/Cargo.toml new file mode 100644 index 0000000000..f14daf3470 --- /dev/null +++ b/smart-contracts/contracts/test/transfer-purse-to-purse/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "transfer-purse-to-purse" +version = "0.1.0" +authors = ["Henry Till "] +edition = "2018" + +[[bin]] +name = "transfer_purse_to_purse" +path = "src/main.rs" +bench = false +doctest = false +test = false + +[features] +std = ["contract/std", "types/std"] + +[dependencies] +contract = { path = "../../../contract", package = "casperlabs-contract" } +types = { path = "../../../../types", package = "casperlabs-types" } diff --git a/smart-contracts/contracts/test/transfer-purse-to-purse/src/main.rs b/smart-contracts/contracts/test/transfer-purse-to-purse/src/main.rs new file mode 100644 index 0000000000..d20914e6db --- /dev/null +++ b/smart-contracts/contracts/test/transfer-purse-to-purse/src/main.rs @@ -0,0 +1,80 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{format, string::String}; + +use contract::{ + contract_api::{account, runtime, storage, system}, + unwrap_or_revert::UnwrapOrRevert, +}; +use types::{ApiError, Key, URef, U512}; + +const PURSE_MAIN: &str = "purse:main"; +const PURSE_TRANSFER_RESULT: &str = "purse_transfer_result"; +const MAIN_PURSE_BALANCE: &str = "main_purse_balance"; + +const ARG_SOURCE: &str = "source"; +const ARG_TARGET: &str = "target"; +const ARG_AMOUNT: &str = "amount"; + +#[repr(u16)] +enum CustomError { + InvalidSourcePurseKey = 103, + UnexpectedSourcePurseKeyVariant = 104, + InvalidDestinationPurseKey = 105, + UnexpectedDestinationPurseKeyVariant = 106, + UnableToGetBalance = 107, +} + +#[no_mangle] +pub extern "C" fn call() { + let main_purse: URef = account::get_main_purse(); + // add or update `main_purse` if it doesn't exist already + runtime::put_key(PURSE_MAIN, Key::from(main_purse)); + + let src_purse_name: String = runtime::get_named_arg(ARG_SOURCE); + + let src_purse_key = runtime::get_key(&src_purse_name) + .unwrap_or_revert_with(ApiError::User(CustomError::InvalidSourcePurseKey as u16)); + + let src_purse = match src_purse_key.as_uref() { + Some(uref) => uref, + None => runtime::revert(ApiError::User( + CustomError::UnexpectedSourcePurseKeyVariant as u16, + )), + }; + let dst_purse_name: String = runtime::get_named_arg(ARG_TARGET); + + let dst_purse = if !runtime::has_key(&dst_purse_name) { + // If `dst_purse_name` is not in known urefs list then create a new purse + let purse = system::create_purse(); + // and save it in known urefs + runtime::put_key(&dst_purse_name, purse.into()); + purse + } else { + let destination_purse_key = runtime::get_key(&dst_purse_name).unwrap_or_revert_with( + ApiError::User(CustomError::InvalidDestinationPurseKey as u16), + ); + match destination_purse_key.as_uref() { + Some(uref) => *uref, + None => runtime::revert(ApiError::User( + CustomError::UnexpectedDestinationPurseKeyVariant as u16, + )), + } + }; + let amount: U512 = runtime::get_named_arg(ARG_AMOUNT); + + let transfer_result = system::transfer_from_purse_to_purse(*src_purse, dst_purse, amount); + + // Assert is done here + let final_balance = system::get_balance(main_purse) + .unwrap_or_revert_with(ApiError::User(CustomError::UnableToGetBalance as u16)); + + let result = format!("{:?}", transfer_result); + // Add new urefs + let result_key: Key = storage::new_uref(result).into(); + runtime::put_key(PURSE_TRANSFER_RESULT, result_key); + runtime::put_key(MAIN_PURSE_BALANCE, storage::new_uref(final_balance).into()); +} diff --git a/types/Cargo.toml b/types/Cargo.toml new file mode 100644 index 0000000000..d88af9d8fb --- /dev/null +++ b/types/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "casperlabs-types" +version = "0.6.0" # when updating, also update 'html_root_url' in lib.rs +authors = ["Fraser Hutchison "] +edition = "2018" +description = "Types used to allow creation of Wasm contracts and tests for use on the CasperLabs network." +readme = "README.md" +documentation = "https://docs.rs/casperlabs-types" +homepage = "https://casperlabs.io" +repository = "https://github.com/CasperLabs/CasperLabs/tree/master/execution-engine/types" +license-file = "../../LICENSE" + +[features] +default = ["base16/alloc"] +std = ["base16/std"] +gens = ["std", "proptest/std"] +no-unstable-features = [] + +[dependencies] +base16 = { version = "0.2.1", default-features = false } +bitflags = "1" +blake2 = { version = "0.8.1", default-features = false } +failure = { version = "0.1.6", default-features = false, features = ["failure_derive"] } +hex_fmt = "0.3.0" +num-derive = { version = "0.3.0", default-features = false } +num-integer = { version = "0.1.42", default-features = false } +num-traits = { version = "0.2.10", default-features = false } +proptest = { version = "0.9.4", optional = true } +uint = { version = "0.8.2", default-features = false, features = [] } + +[dev-dependencies] +proptest = "0.9.4" +version-sync = "0.8" + +[package.metadata.docs.rs] +features = ["no-unstable-features"] diff --git a/types/README.md b/types/README.md new file mode 100644 index 0000000000..97ce46be35 --- /dev/null +++ b/types/README.md @@ -0,0 +1,14 @@ +# `casperlabs-types` + +[![LOGO](https://raw.githubusercontent.com/CasperLabs/CasperLabs/master/CasperLabs_Logo_Horizontal_RGB.png)](https://casperlabs.io/) + +[![Build Status](https://drone-auto.casperlabs.io/api/badges/CasperLabs/CasperLabs/status.svg?branch=dev)](http://drone-auto.casperlabs.io/CasperLabs/CasperLabs) +[![Crates.io](https://img.shields.io/crates/v/casperlabs-types)](https://crates.io/crates/casperlabs-types) +[![Documentation](https://docs.rs/casperlabs-types/badge.svg)](https://docs.rs/casperlabs-types) +[![License](https://img.shields.io/badge/license-COSL-blue.svg)](https://github.com/CasperLabs/CasperLabs/blob/master/LICENSE) + +Types used to allow creation of Wasm contracts and tests for use on the CasperLabs network. + +## License + +Licensed under the [CasperLabs Open Source License (COSL)](https://github.com/CasperLabs/CasperLabs/blob/master/LICENSE). diff --git a/types/benches/bytesrepr_bench.rs b/types/benches/bytesrepr_bench.rs new file mode 100644 index 0000000000..182dd24c64 --- /dev/null +++ b/types/benches/bytesrepr_bench.rs @@ -0,0 +1,509 @@ +#![feature(test)] + +extern crate test; + +use std::{collections::BTreeMap, iter}; + +use test::{black_box, Bencher}; + +use casperlabs_types::{ + account::AccountHash, + bytesrepr::{self, FromBytes, ToBytes}, + AccessRights, CLTyped, CLValue, Key, URef, U128, U256, U512, +}; + +static KB: usize = 1024; +static BATCH: usize = 4 * KB; + +const TEST_I32: i32 = 123_456_789; +const TEST_U128: U128 = U128([123_456_789, 0]); +const TEST_U256: U256 = U256([123_456_789, 0, 0, 0]); +const TEST_U512: U512 = U512([123_456_789, 0, 0, 0, 0, 0, 0, 0]); +const TEST_STR_1: &str = "String One"; +const TEST_STR_2: &str = "String Two"; + +fn prepare_vector(size: usize) -> Vec { + (0..size as i32).collect() +} + +#[bench] +fn serialize_vector_of_i32s(b: &mut Bencher) { + let data = prepare_vector(black_box(BATCH)); + b.iter(|| data.to_bytes()); +} + +#[bench] +fn deserialize_vector_of_i32s(b: &mut Bencher) { + let data = prepare_vector(black_box(BATCH)).to_bytes().unwrap(); + b.iter(|| { + let (res, _rem): (Vec, _) = FromBytes::from_bytes(&data).unwrap(); + res + }); +} + +#[bench] +fn serialize_vector_of_u8(b: &mut Bencher) { + // 0, 1, ... 254, 255, 0, 1, ... + let data: Vec = prepare_vector(BATCH) + .into_iter() + .map(|value| value as u8) + .collect::>(); + b.iter(|| data.to_bytes()); +} + +#[bench] +fn deserialize_vector_of_u8(b: &mut Bencher) { + // 0, 1, ... 254, 255, 0, 1, ... + let data: Vec = prepare_vector(BATCH) + .into_iter() + .map(|value| value as u8) + .collect::>() + .to_bytes() + .unwrap(); + b.iter(|| Vec::::from_bytes(&data)) +} + +#[bench] +fn serialize_u8(b: &mut Bencher) { + b.iter(|| ToBytes::to_bytes(black_box(&129u8))); +} + +#[bench] +fn deserialize_u8(b: &mut Bencher) { + b.iter(|| u8::from_bytes(black_box(&[129u8]))); +} + +#[bench] +fn serialize_i32(b: &mut Bencher) { + b.iter(|| ToBytes::to_bytes(black_box(&1_816_142_132i32))); +} + +#[bench] +fn deserialize_i32(b: &mut Bencher) { + b.iter(|| i32::from_bytes(black_box(&[0x34, 0x21, 0x40, 0x6c]))); +} + +#[bench] +fn serialize_u64(b: &mut Bencher) { + b.iter(|| ToBytes::to_bytes(black_box(&14_157_907_845_468_752_670u64))); +} + +#[bench] +fn deserialize_u64(b: &mut Bencher) { + b.iter(|| u64::from_bytes(black_box(&[0x1e, 0x8b, 0xe1, 0x73, 0x2c, 0xfe, 0x7a, 0xc4]))); +} + +#[bench] +fn serialize_some_u64(b: &mut Bencher) { + let data = Some(14_157_907_845_468_752_670u64); + + b.iter(|| ToBytes::to_bytes(black_box(&data))); +} + +#[bench] +fn deserialize_some_u64(b: &mut Bencher) { + let data = Some(14_157_907_845_468_752_670u64); + let data = data.to_bytes().unwrap(); + + b.iter(|| Option::::from_bytes(&data)); +} + +#[bench] +fn serialize_none_u64(b: &mut Bencher) { + let data: Option = None; + + b.iter(|| ToBytes::to_bytes(black_box(&data))); +} + +#[bench] +fn deserialize_ok_u64(b: &mut Bencher) { + let data: Option = None; + let data = data.to_bytes().unwrap(); + b.iter(|| Option::::from_bytes(&data)); +} + +#[bench] +fn serialize_vector_of_vector_of_u8(b: &mut Bencher) { + let data: Vec> = (0..4) + .map(|_v| { + // 0, 1, 2, ..., 254, 255 + iter::repeat_with(|| 0..255u8) + .flatten() + // 4 times to create 4x 1024 bytes + .take(4) + .collect::>() + }) + .collect::>>(); + + b.iter(|| data.to_bytes()); +} + +#[bench] +fn deserialize_vector_of_vector_of_u8(b: &mut Bencher) { + let data: Vec = (0..4) + .map(|_v| { + // 0, 1, 2, ..., 254, 255 + iter::repeat_with(|| 0..255u8) + .flatten() + // 4 times to create 4x 1024 bytes + .take(4) + .collect::>() + }) + .collect::>>() + .to_bytes() + .unwrap(); + b.iter(|| Vec::>::from_bytes(&data)); +} + +#[bench] +fn serialize_tree_map(b: &mut Bencher) { + let data = { + let mut res = BTreeMap::new(); + res.insert("asdf".to_string(), "zxcv".to_string()); + res.insert("qwer".to_string(), "rewq".to_string()); + res.insert("1234".to_string(), "5678".to_string()); + res + }; + + b.iter(|| ToBytes::to_bytes(black_box(&data))); +} + +#[bench] +fn deserialize_treemap(b: &mut Bencher) { + let data = { + let mut res = BTreeMap::new(); + res.insert("asdf".to_string(), "zxcv".to_string()); + res.insert("qwer".to_string(), "rewq".to_string()); + res.insert("1234".to_string(), "5678".to_string()); + res + }; + let data = data.to_bytes().unwrap(); + b.iter(|| BTreeMap::::from_bytes(black_box(&data))); +} + +#[bench] +fn serialize_string(b: &mut Bencher) { + let lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + let data = lorem.to_string(); + b.iter(|| ToBytes::to_bytes(black_box(&data))); +} + +#[bench] +fn deserialize_string(b: &mut Bencher) { + let lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + let data = lorem.to_bytes().unwrap(); + b.iter(|| String::from_bytes(&data)); +} + +#[bench] +fn serialize_vec_of_string(b: &mut Bencher) { + let lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.".to_string(); + let array_of_lorem: Vec = lorem.split(' ').map(Into::into).collect(); + let data = array_of_lorem; + b.iter(|| ToBytes::to_bytes(black_box(&data))); +} + +#[bench] +fn deserialize_vec_of_string(b: &mut Bencher) { + let lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.".to_string(); + let array_of_lorem: Vec = lorem.split(' ').map(Into::into).collect(); + let data = array_of_lorem.to_bytes().unwrap(); + + b.iter(|| Vec::::from_bytes(&data)); +} + +#[bench] +fn serialize_unit(b: &mut Bencher) { + b.iter(|| ToBytes::to_bytes(black_box(&()))) +} + +#[bench] +fn deserialize_unit(b: &mut Bencher) { + let data = ().to_bytes().unwrap(); + + b.iter(|| <()>::from_bytes(&data)) +} + +#[bench] +fn serialize_key_account(b: &mut Bencher) { + let account = Key::Account(AccountHash::new([0u8; 32])); + + b.iter(|| ToBytes::to_bytes(black_box(&account))) +} + +#[bench] +fn deserialize_key_account(b: &mut Bencher) { + let account = Key::Account(AccountHash::new([0u8; 32])); + let account_bytes = account.to_bytes().unwrap(); + + b.iter(|| Key::from_bytes(black_box(&account_bytes))) +} + +#[bench] +fn serialize_key_hash(b: &mut Bencher) { + let hash = Key::Hash([0u8; 32]); + b.iter(|| ToBytes::to_bytes(black_box(&hash))) +} + +#[bench] +fn deserialize_key_hash(b: &mut Bencher) { + let hash = Key::Hash([0u8; 32]); + let hash_bytes = hash.to_bytes().unwrap(); + + b.iter(|| Key::from_bytes(black_box(&hash_bytes))) +} + +#[bench] +fn serialize_key_uref(b: &mut Bencher) { + let uref = Key::URef(URef::new([0u8; 32], AccessRights::ADD_WRITE)); + b.iter(|| ToBytes::to_bytes(black_box(&uref))) +} + +#[bench] +fn deserialize_key_uref(b: &mut Bencher) { + let uref = Key::URef(URef::new([0u8; 32], AccessRights::ADD_WRITE)); + let uref_bytes = uref.to_bytes().unwrap(); + + b.iter(|| Key::from_bytes(black_box(&uref_bytes))) +} + +#[bench] +fn serialize_vec_of_keys(b: &mut Bencher) { + let keys: Vec = (0..32) + .map(|i| Key::URef(URef::new([i; 32], AccessRights::ADD_WRITE))) + .collect(); + b.iter(|| ToBytes::to_bytes(black_box(&keys))) +} + +#[bench] +fn deserialize_vec_of_keys(b: &mut Bencher) { + let keys: Vec = (0..32) + .map(|i| Key::URef(URef::new([i; 32], AccessRights::ADD_WRITE))) + .collect(); + let keys_bytes = keys.to_bytes().unwrap(); + b.iter(|| Vec::::from_bytes(black_box(&keys_bytes))); +} + +#[bench] +fn serialize_access_rights_read(b: &mut Bencher) { + b.iter(|| AccessRights::READ.to_bytes()); +} + +#[bench] +fn deserialize_access_rights_read(b: &mut Bencher) { + let data = AccessRights::READ.to_bytes().unwrap(); + b.iter(|| AccessRights::from_bytes(&data)); +} + +#[bench] +fn serialize_access_rights_write(b: &mut Bencher) { + b.iter(|| AccessRights::WRITE.to_bytes()); +} + +#[bench] +fn deserialize_access_rights_write(b: &mut Bencher) { + let data = AccessRights::WRITE.to_bytes().unwrap(); + b.iter(|| AccessRights::from_bytes(&data)); +} + +#[bench] +fn serialize_access_rights_add(b: &mut Bencher) { + b.iter(|| AccessRights::ADD.to_bytes()); +} + +#[bench] +fn deserialize_access_rights_add(b: &mut Bencher) { + let data = AccessRights::ADD.to_bytes().unwrap(); + b.iter(|| AccessRights::from_bytes(&data)); +} + +#[bench] +fn serialize_access_rights_read_add(b: &mut Bencher) { + b.iter(|| AccessRights::READ_ADD.to_bytes()); +} + +#[bench] +fn deserialize_access_rights_read_add(b: &mut Bencher) { + let data = AccessRights::READ_ADD.to_bytes().unwrap(); + b.iter(|| AccessRights::from_bytes(&data)); +} + +#[bench] +fn serialize_access_rights_read_write(b: &mut Bencher) { + b.iter(|| AccessRights::READ_WRITE.to_bytes()); +} + +#[bench] +fn deserialize_access_rights_read_write(b: &mut Bencher) { + let data = AccessRights::READ_WRITE.to_bytes().unwrap(); + b.iter(|| AccessRights::from_bytes(&data)); +} + +#[bench] +fn serialize_access_rights_add_write(b: &mut Bencher) { + b.iter(|| AccessRights::ADD_WRITE.to_bytes()); +} + +#[bench] +fn deserialize_access_rights_add_write(b: &mut Bencher) { + let data = AccessRights::ADD_WRITE.to_bytes().unwrap(); + b.iter(|| AccessRights::from_bytes(&data)); +} + +fn serialize_cl_value(raw_value: T) -> Vec { + CLValue::from_t(raw_value) + .expect("should create CLValue") + .to_bytes() + .expect("should serialize CLValue") +} + +fn benchmark_deserialization(b: &mut Bencher, raw_value: T) { + let serialized_value = serialize_cl_value(raw_value); + b.iter(|| { + let cl_value: CLValue = bytesrepr::deserialize(serialized_value.clone()).unwrap(); + let _raw_value: T = cl_value.into_t().unwrap(); + }); +} + +#[bench] +fn serialize_cl_value_int32(b: &mut Bencher) { + b.iter(|| serialize_cl_value(TEST_I32)); +} + +#[bench] +fn deserialize_cl_value_int32(b: &mut Bencher) { + benchmark_deserialization(b, TEST_I32); +} + +#[bench] +fn serialize_cl_value_uint128(b: &mut Bencher) { + b.iter(|| serialize_cl_value(TEST_U128)); +} + +#[bench] +fn deserialize_cl_value_uint128(b: &mut Bencher) { + benchmark_deserialization(b, TEST_U128); +} + +#[bench] +fn serialize_cl_value_uint256(b: &mut Bencher) { + b.iter(|| serialize_cl_value(TEST_U256)); +} + +#[bench] +fn deserialize_cl_value_uint256(b: &mut Bencher) { + benchmark_deserialization(b, TEST_U256); +} + +#[bench] +fn serialize_cl_value_uint512(b: &mut Bencher) { + b.iter(|| serialize_cl_value(TEST_U512)); +} + +#[bench] +fn deserialize_cl_value_uint512(b: &mut Bencher) { + benchmark_deserialization(b, TEST_U512); +} + +#[bench] +fn serialize_cl_value_bytearray(b: &mut Bencher) { + b.iter(|| serialize_cl_value((0..255).collect::>())); +} + +#[bench] +fn deserialize_cl_value_bytearray(b: &mut Bencher) { + benchmark_deserialization(b, (0..255).collect::>()); +} + +#[bench] +fn serialize_cl_value_listint32(b: &mut Bencher) { + b.iter(|| serialize_cl_value((0..1024).collect::>())); +} + +#[bench] +fn deserialize_cl_value_listint32(b: &mut Bencher) { + benchmark_deserialization(b, (0..1024).collect::>()); +} + +#[bench] +fn serialize_cl_value_string(b: &mut Bencher) { + b.iter(|| serialize_cl_value(TEST_STR_1.to_string())); +} + +#[bench] +fn deserialize_cl_value_string(b: &mut Bencher) { + benchmark_deserialization(b, TEST_STR_1.to_string()); +} + +#[bench] +fn serialize_cl_value_liststring(b: &mut Bencher) { + b.iter(|| serialize_cl_value(vec![TEST_STR_1.to_string(), TEST_STR_2.to_string()])); +} + +#[bench] +fn deserialize_cl_value_liststring(b: &mut Bencher) { + benchmark_deserialization(b, vec![TEST_STR_1.to_string(), TEST_STR_2.to_string()]); +} + +#[bench] +fn serialize_cl_value_namedkey(b: &mut Bencher) { + b.iter(|| { + serialize_cl_value(( + TEST_STR_1.to_string(), + Key::Account(AccountHash::new([0xffu8; 32])), + )) + }); +} + +#[bench] +fn deserialize_cl_value_namedkey(b: &mut Bencher) { + benchmark_deserialization( + b, + ( + TEST_STR_1.to_string(), + Key::Account(AccountHash::new([0xffu8; 32])), + ), + ); +} + +#[bench] +fn serialize_u128(b: &mut Bencher) { + let num_u128 = U128::default(); + b.iter(|| ToBytes::to_bytes(black_box(&num_u128))) +} + +#[bench] +fn deserialize_u128(b: &mut Bencher) { + let num_u128 = U128::default(); + let num_u128_bytes = num_u128.to_bytes().unwrap(); + + b.iter(|| U128::from_bytes(black_box(&num_u128_bytes))) +} + +#[bench] +fn serialize_u256(b: &mut Bencher) { + let num_u256 = U256::default(); + b.iter(|| ToBytes::to_bytes(black_box(&num_u256))) +} + +#[bench] +fn deserialize_u256(b: &mut Bencher) { + let num_u256 = U256::default(); + let num_u256_bytes = num_u256.to_bytes().unwrap(); + + b.iter(|| U256::from_bytes(black_box(&num_u256_bytes))) +} + +#[bench] +fn serialize_u512(b: &mut Bencher) { + let num_u512 = U512::default(); + b.iter(|| ToBytes::to_bytes(black_box(&num_u512))) +} + +#[bench] +fn deserialize_u512(b: &mut Bencher) { + let num_u512 = U512::default(); + let num_u512_bytes = num_u512.to_bytes().unwrap(); + + b.iter(|| U512::from_bytes(black_box(&num_u512_bytes))) +} diff --git a/types/src/access_rights.rs b/types/src/access_rights.rs new file mode 100644 index 0000000000..2eff471199 --- /dev/null +++ b/types/src/access_rights.rs @@ -0,0 +1,146 @@ +use alloc::vec::Vec; + +use bitflags::bitflags; + +use crate::bytesrepr; + +/// The number of bytes in a serialized [`AccessRights`]. +pub const ACCESS_RIGHTS_SERIALIZED_LENGTH: usize = 1; + +bitflags! { + /// A struct which behaves like a set of bitflags to define access rights associated with a + /// [`URef`](crate::URef). + #[allow(clippy::derive_hash_xor_eq)] + pub struct AccessRights: u8 { + /// No permissions + const NONE = 0; + /// Permission to read the value under the associated `URef`. + const READ = 0b001; + /// Permission to write a value under the associated `URef`. + const WRITE = 0b010; + /// Permission to add to the value under the associated `URef`. + const ADD = 0b100; + /// Permission to read or add to the value under the associated `URef`. + const READ_ADD = Self::READ.bits | Self::ADD.bits; + /// Permission to read or write the value under the associated `URef`. + const READ_WRITE = Self::READ.bits | Self::WRITE.bits; + /// Permission to add to, or write the value under the associated `URef`. + const ADD_WRITE = Self::ADD.bits | Self::WRITE.bits; + /// Permission to read, add to, or write the value under the associated `URef`. + const READ_ADD_WRITE = Self::READ.bits | Self::ADD.bits | Self::WRITE.bits; + } +} + +impl Default for AccessRights { + fn default() -> Self { + AccessRights::NONE + } +} + +impl AccessRights { + /// Returns `true` if the `READ` flag is set. + pub fn is_readable(self) -> bool { + self & AccessRights::READ == AccessRights::READ + } + + /// Returns `true` if the `WRITE` flag is set. + pub fn is_writeable(self) -> bool { + self & AccessRights::WRITE == AccessRights::WRITE + } + + /// Returns `true` if the `ADD` flag is set. + pub fn is_addable(self) -> bool { + self & AccessRights::ADD == AccessRights::ADD + } + + /// Returns `true` if no flags are set. + pub fn is_none(self) -> bool { + self == AccessRights::NONE + } +} + +impl core::fmt::Display for AccessRights { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match *self { + AccessRights::NONE => write!(f, "NONE"), + AccessRights::READ => write!(f, "READ"), + AccessRights::WRITE => write!(f, "WRITE"), + AccessRights::ADD => write!(f, "ADD"), + AccessRights::READ_ADD => write!(f, "READ_ADD"), + AccessRights::READ_WRITE => write!(f, "READ_WRITE"), + AccessRights::ADD_WRITE => write!(f, "ADD_WRITE"), + AccessRights::READ_ADD_WRITE => write!(f, "READ_ADD_WRITE"), + _ => write!(f, "UNKNOWN"), + } + } +} + +impl bytesrepr::ToBytes for AccessRights { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + self.bits.to_bytes() + } + + fn serialized_length(&self) -> usize { + ACCESS_RIGHTS_SERIALIZED_LENGTH + } +} + +impl bytesrepr::FromBytes for AccessRights { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (id, rem) = u8::from_bytes(bytes)?; + match AccessRights::from_bits(id) { + Some(rights) => Ok((rights, rem)), + None => Err(bytesrepr::Error::Formatting), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_readable(right: AccessRights, is_true: bool) { + assert_eq!(right.is_readable(), is_true) + } + + #[test] + fn test_is_readable() { + test_readable(AccessRights::READ, true); + test_readable(AccessRights::READ_ADD, true); + test_readable(AccessRights::READ_WRITE, true); + test_readable(AccessRights::READ_ADD_WRITE, true); + test_readable(AccessRights::ADD, false); + test_readable(AccessRights::ADD_WRITE, false); + test_readable(AccessRights::WRITE, false); + } + + fn test_writable(right: AccessRights, is_true: bool) { + assert_eq!(right.is_writeable(), is_true) + } + + #[test] + fn test_is_writable() { + test_writable(AccessRights::WRITE, true); + test_writable(AccessRights::READ_WRITE, true); + test_writable(AccessRights::ADD_WRITE, true); + test_writable(AccessRights::READ, false); + test_writable(AccessRights::ADD, false); + test_writable(AccessRights::READ_ADD, false); + test_writable(AccessRights::READ_ADD_WRITE, true); + } + + fn test_addable(right: AccessRights, is_true: bool) { + assert_eq!(right.is_addable(), is_true) + } + + #[test] + fn test_is_addable() { + test_addable(AccessRights::ADD, true); + test_addable(AccessRights::READ_ADD, true); + test_addable(AccessRights::READ_WRITE, false); + test_addable(AccessRights::ADD_WRITE, true); + test_addable(AccessRights::READ, false); + test_addable(AccessRights::WRITE, false); + test_addable(AccessRights::READ_ADD_WRITE, true); + } +} diff --git a/types/src/account.rs b/types/src/account.rs new file mode 100644 index 0000000000..74b28877d7 --- /dev/null +++ b/types/src/account.rs @@ -0,0 +1,406 @@ +//! Contains types and constants associated with user accounts. + +use alloc::{boxed::Box, vec::Vec}; +use core::{ + convert::TryFrom, + fmt::{Debug, Display, Formatter}, +}; + +use failure::Fail; + +use crate::{ + bytesrepr::{Error, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + CLType, CLTyped, +}; + +// This error type is not intended to be used by third party crates. +#[doc(hidden)] +#[derive(Debug, Eq, PartialEq)] +pub struct TryFromIntError(()); + +/// Associated error type of `TryFrom<&[u8]>` for [`AccountHash`]. +#[derive(Debug)] +pub struct TryFromSliceForAccountHashError(()); + +/// The various types of action which can be performed in the context of a given account. +#[repr(u32)] +pub enum ActionType { + /// Represents performing a deploy. + Deployment = 0, + /// Represents changing the associated keys (i.e. map of [`AccountHash`]s to [`Weight`]s) or + /// action thresholds (i.e. the total [`Weight`]s of signing [`AccountHash`]s required to + /// perform various actions). + KeyManagement = 1, +} + +// This conversion is not intended to be used by third party crates. +#[doc(hidden)] +impl TryFrom for ActionType { + type Error = TryFromIntError; + + fn try_from(value: u32) -> Result { + // This doesn't use `num_derive` traits such as FromPrimitive and ToPrimitive + // that helps to automatically create `from_u32` and `to_u32`. This approach + // gives better control over generated code. + match value { + d if d == ActionType::Deployment as u32 => Ok(ActionType::Deployment), + d if d == ActionType::KeyManagement as u32 => Ok(ActionType::KeyManagement), + _ => Err(TryFromIntError(())), + } + } +} + +/// Errors that can occur while changing action thresholds (i.e. the total [`Weight`]s of signing +/// [`AccountHash`]s required to perform various actions) on an account. +#[repr(i32)] +#[derive(Debug, Fail, PartialEq, Eq, Copy, Clone)] +pub enum SetThresholdFailure { + /// Setting the key-management threshold to a value lower than the deployment threshold is + /// disallowed. + #[fail(display = "New threshold should be greater than or equal to deployment threshold")] + KeyManagementThreshold = 1, + /// Setting the deployment threshold to a value greater than any other threshold is disallowed. + #[fail(display = "New threshold should be lower than or equal to key management threshold")] + DeploymentThreshold = 2, + /// Caller doesn't have sufficient permissions to set new thresholds. + #[fail(display = "Unable to set action threshold due to insufficient permissions")] + PermissionDeniedError = 3, + /// Setting a threshold to a value greater than the total weight of associated keys is + /// disallowed. + #[fail( + display = "New threshold should be lower or equal than total weight of associated keys" + )] + InsufficientTotalWeight = 4, +} + +// This conversion is not intended to be used by third party crates. +#[doc(hidden)] +impl TryFrom for SetThresholdFailure { + type Error = TryFromIntError; + + fn try_from(value: i32) -> Result { + match value { + d if d == SetThresholdFailure::KeyManagementThreshold as i32 => { + Ok(SetThresholdFailure::KeyManagementThreshold) + } + d if d == SetThresholdFailure::DeploymentThreshold as i32 => { + Ok(SetThresholdFailure::DeploymentThreshold) + } + d if d == SetThresholdFailure::PermissionDeniedError as i32 => { + Ok(SetThresholdFailure::PermissionDeniedError) + } + d if d == SetThresholdFailure::InsufficientTotalWeight as i32 => { + Ok(SetThresholdFailure::InsufficientTotalWeight) + } + _ => Err(TryFromIntError(())), + } + } +} + +/// Maximum number of associated keys (i.e. map of [`AccountHash`]s to [`Weight`]s) for a single +/// account. +pub const MAX_ASSOCIATED_KEYS: usize = 10; + +/// The number of bytes in a serialized [`Weight`]. +pub const WEIGHT_SERIALIZED_LENGTH: usize = U8_SERIALIZED_LENGTH; + +/// The weight attributed to a given [`AccountHash`] in an account's associated keys. +#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug)] +pub struct Weight(u8); + +impl Weight { + /// Constructs a new `Weight`. + pub fn new(weight: u8) -> Weight { + Weight(weight) + } + + /// Returns the value of `self` as a `u8`. + pub fn value(self) -> u8 { + self.0 + } +} + +impl ToBytes for Weight { + fn to_bytes(&self) -> Result, Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + WEIGHT_SERIALIZED_LENGTH + } +} + +impl FromBytes for Weight { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (byte, rem) = u8::from_bytes(bytes)?; + Ok((Weight::new(byte), rem)) + } +} + +impl CLTyped for Weight { + fn cl_type() -> CLType { + CLType::U8 + } +} + +/// The length in bytes of a [`AccountHash`]. +pub const ACCOUNT_HASH_LENGTH: usize = 32; + +/// The number of bytes in a serialized [`AccountHash`]. +pub const ACCOUNT_HASH_SERIALIZED_LENGTH: usize = 32; + +/// A type alias for the raw bytes of an Account Hash. +pub type AccountHashBytes = [u8; ACCOUNT_HASH_LENGTH]; + +/// A newtype wrapping a [`AccountHashBytes`] which is the raw bytes of +/// the AccountHash, a hash of Public Key and Algorithm +#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)] +pub struct AccountHash(AccountHashBytes); + +impl AccountHash { + /// Constructs a new `AccountHash` instance from the raw bytes of an Public Key Account Hash. + pub const fn new(value: AccountHashBytes) -> AccountHash { + AccountHash(value) + } + + /// Returns the raw bytes of the account hash as an array. + pub fn value(&self) -> AccountHashBytes { + self.0 + } + + /// Returns the raw bytes of the account hash as a `slice`. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } +} + +impl TryFrom<&[u8]> for AccountHash { + type Error = TryFromSliceForAccountHashError; + + fn try_from(bytes: &[u8]) -> Result { + AccountHashBytes::try_from(bytes) + .map(AccountHash::new) + .map_err(|_| TryFromSliceForAccountHashError(())) + } +} + +impl TryFrom<&alloc::vec::Vec> for AccountHash { + type Error = TryFromSliceForAccountHashError; + + fn try_from(bytes: &Vec) -> Result { + AccountHashBytes::try_from(bytes as &[u8]) + .map(AccountHash::new) + .map_err(|_| TryFromSliceForAccountHashError(())) + } +} + +impl Display for AccountHash { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", base16::encode_lower(&self.0)) + } +} + +impl Debug for AccountHash { + fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { + write!(f, "AccountHash({})", base16::encode_lower(&self.0)) + } +} + +impl CLTyped for AccountHash { + fn cl_type() -> CLType { + CLType::FixedList(Box::new(CLType::U8), 32) + } +} + +impl ToBytes for AccountHash { + fn to_bytes(&self) -> Result, Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + ACCOUNT_HASH_SERIALIZED_LENGTH + } +} + +impl FromBytes for AccountHash { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (bytes, rem) = <[u8; 32]>::from_bytes(bytes)?; + Ok((AccountHash::new(bytes), rem)) + } +} + +/// Errors that can occur while adding a new [`AccountHash`] to an account's associated keys map. +#[derive(PartialEq, Eq, Fail, Debug, Copy, Clone)] +#[repr(i32)] +pub enum AddKeyFailure { + /// There are already [`MAX_ASSOCIATED_KEYS`] [`AccountHash`]s associated with the given + /// account. + #[fail(display = "Unable to add new associated key because maximum amount of keys is reached")] + MaxKeysLimit = 1, + /// The given [`AccountHash`] is already associated with the given account. + #[fail(display = "Unable to add new associated key because given key already exists")] + DuplicateKey = 2, + /// Caller doesn't have sufficient permissions to associate a new [`AccountHash`] with the + /// given account. + #[fail(display = "Unable to add new associated key due to insufficient permissions")] + PermissionDenied = 3, +} + +// This conversion is not intended to be used by third party crates. +#[doc(hidden)] +impl TryFrom for AddKeyFailure { + type Error = TryFromIntError; + + fn try_from(value: i32) -> Result { + match value { + d if d == AddKeyFailure::MaxKeysLimit as i32 => Ok(AddKeyFailure::MaxKeysLimit), + d if d == AddKeyFailure::DuplicateKey as i32 => Ok(AddKeyFailure::DuplicateKey), + d if d == AddKeyFailure::PermissionDenied as i32 => Ok(AddKeyFailure::PermissionDenied), + _ => Err(TryFromIntError(())), + } + } +} + +/// Errors that can occur while removing a [`AccountHash`] from an account's associated keys map. +#[derive(Fail, Debug, Eq, PartialEq, Copy, Clone)] +#[repr(i32)] +pub enum RemoveKeyFailure { + /// The given [`AccountHash`] is not associated with the given account. + #[fail(display = "Unable to remove a key that does not exist")] + MissingKey = 1, + /// Caller doesn't have sufficient permissions to remove an associated [`AccountHash`] from the + /// given account. + #[fail(display = "Unable to remove associated key due to insufficient permissions")] + PermissionDenied = 2, + /// Removing the given associated [`AccountHash`] would cause the total weight of all remaining + /// `AccountHash`s to fall below one of the action thresholds for the given account. + #[fail(display = "Unable to remove a key which would violate action threshold constraints")] + ThresholdViolation = 3, +} + +// This conversion is not intended to be used by third party crates. +#[doc(hidden)] +impl TryFrom for RemoveKeyFailure { + type Error = TryFromIntError; + + fn try_from(value: i32) -> Result { + match value { + d if d == RemoveKeyFailure::MissingKey as i32 => Ok(RemoveKeyFailure::MissingKey), + d if d == RemoveKeyFailure::PermissionDenied as i32 => { + Ok(RemoveKeyFailure::PermissionDenied) + } + d if d == RemoveKeyFailure::ThresholdViolation as i32 => { + Ok(RemoveKeyFailure::ThresholdViolation) + } + _ => Err(TryFromIntError(())), + } + } +} + +/// Errors that can occur while updating the [`Weight`] of a [`AccountHash`] in an account's +/// associated keys map. +#[derive(PartialEq, Eq, Fail, Debug, Copy, Clone)] +#[repr(i32)] +pub enum UpdateKeyFailure { + /// The given [`AccountHash`] is not associated with the given account. + #[fail(display = "Unable to update the value under an associated key that does not exist")] + MissingKey = 1, + /// Caller doesn't have sufficient permissions to update an associated [`AccountHash`] from the + /// given account. + #[fail(display = "Unable to update associated key due to insufficient permissions")] + PermissionDenied = 2, + /// Updating the [`Weight`] of the given associated [`AccountHash`] would cause the total + /// weight of all `AccountHash`s to fall below one of the action thresholds for the given + /// account. + #[fail(display = "Unable to update weight that would fall below any of action thresholds")] + ThresholdViolation = 3, +} + +// This conversion is not intended to be used by third party crates. +#[doc(hidden)] +impl TryFrom for UpdateKeyFailure { + type Error = TryFromIntError; + + fn try_from(value: i32) -> Result { + match value { + d if d == UpdateKeyFailure::MissingKey as i32 => Ok(UpdateKeyFailure::MissingKey), + d if d == UpdateKeyFailure::PermissionDenied as i32 => { + Ok(UpdateKeyFailure::PermissionDenied) + } + d if d == UpdateKeyFailure::ThresholdViolation as i32 => { + Ok(UpdateKeyFailure::ThresholdViolation) + } + _ => Err(TryFromIntError(())), + } + } +} + +#[cfg(test)] +mod tests { + use std::{convert::TryFrom, vec::Vec}; + + use super::*; + + #[test] + fn account_hash_from_slice() { + let bytes: Vec = (0..32).collect(); + let account_hash = AccountHash::try_from(&bytes[..]).expect("should create account hash"); + assert_eq!(&bytes, &account_hash.as_bytes()); + } + + #[test] + fn account_hash_from_slice_too_small() { + let _account_hash = + AccountHash::try_from(&[0u8; 31][..]).expect_err("should not create account hash"); + } + + #[test] + fn account_hash_from_slice_too_big() { + let _account_hash = + AccountHash::try_from(&[0u8; 33][..]).expect_err("should not create account hash"); + } + + #[test] + fn try_from_i32_for_set_threshold_failure() { + let max_valid_value_for_variant = SetThresholdFailure::InsufficientTotalWeight as i32; + assert_eq!( + Err(TryFromIntError(())), + SetThresholdFailure::try_from(max_valid_value_for_variant + 1), + "Did you forget to update `SetThresholdFailure::try_from` for a new variant of \ + `SetThresholdFailure`, or `max_valid_value_for_variant` in this test?" + ); + } + + #[test] + fn try_from_i32_for_add_key_failure() { + let max_valid_value_for_variant = AddKeyFailure::PermissionDenied as i32; + assert_eq!( + Err(TryFromIntError(())), + AddKeyFailure::try_from(max_valid_value_for_variant + 1), + "Did you forget to update `AddKeyFailure::try_from` for a new variant of \ + `AddKeyFailure`, or `max_valid_value_for_variant` in this test?" + ); + } + + #[test] + fn try_from_i32_for_remove_key_failure() { + let max_valid_value_for_variant = RemoveKeyFailure::ThresholdViolation as i32; + assert_eq!( + Err(TryFromIntError(())), + RemoveKeyFailure::try_from(max_valid_value_for_variant + 1), + "Did you forget to update `RemoveKeyFailure::try_from` for a new variant of \ + `RemoveKeyFailure`, or `max_valid_value_for_variant` in this test?" + ); + } + + #[test] + fn try_from_i32_for_update_key_failure() { + let max_valid_value_for_variant = UpdateKeyFailure::ThresholdViolation as i32; + assert_eq!( + Err(TryFromIntError(())), + UpdateKeyFailure::try_from(max_valid_value_for_variant + 1), + "Did you forget to update `UpdateKeyFailure::try_from` for a new variant of \ + `UpdateKeyFailure`, or `max_valid_value_for_variant` in this test?" + ); + } +} diff --git a/types/src/api_error.rs b/types/src/api_error.rs new file mode 100644 index 0000000000..85e07e0265 --- /dev/null +++ b/types/src/api_error.rs @@ -0,0 +1,833 @@ +//! Contains [`ApiError`] and associated helper functions. + +use core::{ + fmt::{self, Debug, Formatter}, + u16, u8, +}; + +use crate::{ + account::{ + AddKeyFailure, RemoveKeyFailure, SetThresholdFailure, TryFromIntError, + TryFromSliceForAccountHashError, UpdateKeyFailure, + }, + bytesrepr, contracts, + system_contract_errors::{mint, pos}, + CLValueError, +}; + +/// All `Error` variants defined in this library other than `Error::User` will convert to a `u32` +/// value less than or equal to `RESERVED_ERROR_MAX`. +const RESERVED_ERROR_MAX: u32 = u16::MAX as u32; // 0..=65535 + +/// Proof of Stake errors (defined in "contracts/system/pos/src/error.rs") will have this value +/// added to them when being converted to a `u32`. +const POS_ERROR_OFFSET: u32 = RESERVED_ERROR_MAX - u8::MAX as u32; // 65280..=65535 + +/// Mint errors (defined in "contracts/system/mint/src/error.rs") will have this value +/// added to them when being converted to a `u32`. +const MINT_ERROR_OFFSET: u32 = (POS_ERROR_OFFSET - 1) - u8::MAX as u32; // 65024..=65279 + +/// Contract header errors (defined in "types/src/contract_header.rs") will have this value +/// added to them when being converted to a `u32`. +const HEADER_ERROR_OFFSET: u32 = (MINT_ERROR_OFFSET - 1) - u8::MAX as u32; // 64768..=65023 + +/// Minimum value of user error's inclusive range. +const USER_ERROR_MIN: u32 = RESERVED_ERROR_MAX + 1; + +/// Maximum value of user error's inclusive range. +const USER_ERROR_MAX: u32 = 2 * RESERVED_ERROR_MAX + 1; + +/// Minimum value of Mint error's inclusive range. +const MINT_ERROR_MIN: u32 = MINT_ERROR_OFFSET; + +/// Maximum value of Mint error's inclusive range. +const MINT_ERROR_MAX: u32 = POS_ERROR_OFFSET - 1; + +/// Minimum value of Proof of Stake error's inclusive range. +const POS_ERROR_MIN: u32 = POS_ERROR_OFFSET; + +/// Maximum value of Proof of Stake error's inclusive range. +const POS_ERROR_MAX: u32 = RESERVED_ERROR_MAX; + +/// Minimum value of contract header error's inclusive range. +const HEADER_ERROR_MIN: u32 = HEADER_ERROR_OFFSET; + +/// Maximum value of contract header error's inclusive range. +const HEADER_ERROR_MAX: u32 = HEADER_ERROR_OFFSET + u8::MAX as u32; + +/// Errors which can be encountered while running a smart contract. +/// +/// An `ApiError` can be converted to a `u32` in order to be passed via the execution engine's +/// `ext_ffi::revert()` function. This means the information each variant can convey is limited. +/// +/// The variants are split into numeric ranges as follows: +/// +/// | Inclusive range | Variant(s) | +/// | ----------------| ---------------------------------------------| +/// | [1, 65023] | all except `Mint`, `ProofOfStake` and `User` | +/// | [65024, 65279] | `Mint` | +/// | [65280, 65535] | `ProofOfStake` | +/// | [65536, 131071] | `User` | +/// +/// ## Mappings +/// +/// The expanded mapping of all variants to their numerical equivalents is as follows: +/// ``` +/// # use casperlabs_types::ApiError::{self, *}; +/// # macro_rules! show_and_check { +/// # ($lhs:literal => $rhs:expr) => { +/// # assert_eq!($lhs as u32, ApiError::from($rhs).into()); +/// # }; +/// # } +/// // General system errors: +/// # show_and_check!( +/// 1 => None +/// # ); +/// # show_and_check!( +/// 2 => MissingArgument +/// # ); +/// # show_and_check!( +/// 3 => InvalidArgument +/// # ); +/// # show_and_check!( +/// 4 => Deserialize +/// # ); +/// # show_and_check!( +/// 5 => Read +/// # ); +/// # show_and_check!( +/// 6 => ValueNotFound +/// # ); +/// # show_and_check!( +/// 7 => ContractNotFound +/// # ); +/// # show_and_check!( +/// 8 => GetKey +/// # ); +/// # show_and_check!( +/// 9 => UnexpectedKeyVariant +/// # ); +/// # show_and_check!( +/// 10 => UnexpectedContractRefVariant +/// # ); +/// # show_and_check!( +/// 11 => InvalidPurseName +/// # ); +/// # show_and_check!( +/// 12 => InvalidPurse +/// # ); +/// # show_and_check!( +/// 13 => UpgradeContractAtURef +/// # ); +/// # show_and_check!( +/// 14 => Transfer +/// # ); +/// # show_and_check!( +/// 15 => NoAccessRights +/// # ); +/// # show_and_check!( +/// 16 => CLTypeMismatch +/// # ); +/// # show_and_check!( +/// 17 => EarlyEndOfStream +/// # ); +/// # show_and_check!( +/// 18 => Formatting +/// # ); +/// # show_and_check!( +/// 19 => LeftOverBytes +/// # ); +/// # show_and_check!( +/// 20 => OutOfMemory +/// # ); +/// # show_and_check!( +/// 21 => MaxKeysLimit +/// # ); +/// # show_and_check!( +/// 22 => DuplicateKey +/// # ); +/// # show_and_check!( +/// 23 => PermissionDenied +/// # ); +/// # show_and_check!( +/// 24 => MissingKey +/// # ); +/// # show_and_check!( +/// 25 => ThresholdViolation +/// # ); +/// # show_and_check!( +/// 26 => KeyManagementThreshold +/// # ); +/// # show_and_check!( +/// 27 => DeploymentThreshold +/// # ); +/// # show_and_check!( +/// 28 => InsufficientTotalWeight +/// # ); +/// # show_and_check!( +/// 29 => InvalidSystemContract +/// # ); +/// # show_and_check!( +/// 30 => PurseNotCreated +/// # ); +/// # show_and_check!( +/// 31 => Unhandled +/// # ); +/// # show_and_check!( +/// 32 => BufferTooSmall +/// # ); +/// # show_and_check!( +/// 33 => HostBufferEmpty +/// # ); +/// # show_and_check!( +/// 34 => HostBufferFull +/// # ); +/// // Contract header errors: +/// use casperlabs_types::contracts::Error as ContractHeaderError; +/// # show_and_check!( +/// 64_769 => ContractHeaderError::PreviouslyUsedVersion +/// # ); +/// # show_and_check!( +/// 64_770 => ContractHeaderError::ContractNotFound +/// # ); +/// # show_and_check!( +/// 64_771 => ContractHeaderError::GroupAlreadyExists +/// # ); +/// # show_and_check!( +/// 64_772 => ContractHeaderError::MaxGroupsExceeded +/// # ); +/// # show_and_check!( +/// 64_773 => ContractHeaderError::MaxTotalURefsExceeded +/// # ); +/// // Mint errors: +/// use casperlabs_types::system_contract_errors::mint::Error as MintError; +/// # show_and_check!( +/// 65_024 => MintError::InsufficientFunds +/// # ); +/// # show_and_check!( +/// 65_025 => MintError::SourceNotFound +/// # ); +/// # show_and_check!( +/// 65_026 => MintError::DestNotFound +/// # ); +/// # show_and_check!( +/// 65_027 => MintError::InvalidURef +/// # ); +/// # show_and_check!( +/// 65_028 => MintError::InvalidAccessRights +/// # ); +/// # show_and_check!( +/// 65_029 => MintError::InvalidNonEmptyPurseCreation +/// # ); +/// # show_and_check!( +/// 65_030 => MintError::Storage +/// # ); +/// # show_and_check!( +/// 65_031 => MintError::PurseNotFound +/// # ); +/// +/// // Proof of stake errors: +/// use casperlabs_types::system_contract_errors::pos::Error as PosError; +/// # show_and_check!( +/// 65_280 => PosError::NotBonded +/// # ); +/// # show_and_check!( +/// 65_281 => PosError::TooManyEventsInQueue +/// # ); +/// # show_and_check!( +/// 65_282 => PosError::CannotUnbondLastValidator +/// # ); +/// # show_and_check!( +/// 65_283 => PosError::SpreadTooHigh +/// # ); +/// # show_and_check!( +/// 65_284 => PosError::MultipleRequests +/// # ); +/// # show_and_check!( +/// 65_285 => PosError::BondTooSmall +/// # ); +/// # show_and_check!( +/// 65_286 => PosError::BondTooLarge +/// # ); +/// # show_and_check!( +/// 65_287 => PosError::UnbondTooLarge +/// # ); +/// # show_and_check!( +/// 65_288 => PosError::BondTransferFailed +/// # ); +/// # show_and_check!( +/// 65_289 => PosError::UnbondTransferFailed +/// # ); +/// # show_and_check!( +/// 65_290 => PosError::TimeWentBackwards +/// # ); +/// # show_and_check!( +/// 65_291 => PosError::StakesNotFound +/// # ); +/// # show_and_check!( +/// 65_292 => PosError::PaymentPurseNotFound +/// # ); +/// # show_and_check!( +/// 65_293 => PosError::PaymentPurseKeyUnexpectedType +/// # ); +/// # show_and_check!( +/// 65_294 => PosError::PaymentPurseBalanceNotFound +/// # ); +/// # show_and_check!( +/// 65_295 => PosError::BondingPurseNotFound +/// # ); +/// # show_and_check!( +/// 65_296 => PosError::BondingPurseKeyUnexpectedType +/// # ); +/// # show_and_check!( +/// 65_297 => PosError::RefundPurseKeyUnexpectedType +/// # ); +/// # show_and_check!( +/// 65_298 => PosError::RewardsPurseNotFound +/// # ); +/// # show_and_check!( +/// 65_299 => PosError::RewardsPurseKeyUnexpectedType +/// # ); +/// # show_and_check!( +/// 65_300 => PosError::StakesKeyDeserializationFailed +/// # ); +/// # show_and_check!( +/// 65_301 => PosError::StakesDeserializationFailed +/// # ); +/// # show_and_check!( +/// 65_302 => PosError::SystemFunctionCalledByUserAccount +/// # ); +/// # show_and_check!( +/// 65_303 => PosError::InsufficientPaymentForAmountSpent +/// # ); +/// # show_and_check!( +/// 65_304 => PosError::FailedTransferToRewardsPurse +/// # ); +/// # show_and_check!( +/// 65_305 => PosError::FailedTransferToAccountPurse +/// # ); +/// # show_and_check!( +/// 65_306 => PosError::SetRefundPurseCalledOutsidePayment +/// # ); +/// +/// // User-defined errors: +/// # show_and_check!( +/// 65_536 => User(0) +/// # ); +/// # show_and_check!( +/// 65_537 => User(1) +/// # ); +/// # show_and_check!( +/// 65_538 => User(2) +/// # ); +/// # show_and_check!( +/// 131_071 => User(u16::max_value()) +/// # ); +/// ``` +/// +/// Users can specify a C-style enum and implement `From` to ease usage of +/// `casperlabs_contract::runtime::revert()`, e.g. +/// ``` +/// use casperlabs_types::ApiError; +/// +/// #[repr(u16)] +/// enum FailureCode { +/// Zero = 0, // 65,536 as an ApiError::User +/// One, // 65,537 as an ApiError::User +/// Two // 65,538 as an ApiError::User +/// } +/// +/// impl From for ApiError { +/// fn from(code: FailureCode) -> Self { +/// ApiError::User(code as u16) +/// } +/// } +/// +/// assert_eq!(ApiError::User(1), FailureCode::One.into()); +/// assert_eq!(65_536, u32::from(ApiError::from(FailureCode::Zero))); +/// assert_eq!(65_538, u32::from(ApiError::from(FailureCode::Two))); +/// ``` +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum ApiError { + /// Optional data was unexpectedly `None`. + None, + /// Specified argument not provided. + MissingArgument, + /// Argument not of correct type. + InvalidArgument, + /// Failed to deserialize a value. + Deserialize, + /// `casperlabs_contract::storage::read()` returned an error. + Read, + /// The given key returned a `None` value. + ValueNotFound, + /// Failed to find a specified contract. + ContractNotFound, + /// A call to `casperlabs_contract::runtime::get_key()` returned a failure. + GetKey, + /// The [`Key`](crate::Key) variant was not as expected. + UnexpectedKeyVariant, + /// The [`ContractRef`](crate::ContractRef) variant was not as expected. + UnexpectedContractRefVariant, + /// Invalid purse name given. + InvalidPurseName, + /// Invalid purse retrieved. + InvalidPurse, + /// Failed to upgrade contract at [`URef`](crate::URef). + UpgradeContractAtURef, + /// Failed to transfer motes. + Transfer, + /// The given [`URef`](crate::URef) has no access rights. + NoAccessRights, + /// A given type could not be constructed from a [`CLValue`](crate::CLValue). + CLTypeMismatch, + /// Early end of stream while deserializing. + EarlyEndOfStream, + /// Formatting error while deserializing. + Formatting, + /// Not all input bytes were consumed in [`deserialize`](crate::bytesrepr::deserialize). + LeftOverBytes, + /// Out of memory error. + OutOfMemory, + /// There are already [`MAX_ASSOCIATED_KEYS`](crate::account::MAX_ASSOCIATED_KEYS) + /// [`AccountHash`](crate::account::AccountHash)s associated with the given account. + MaxKeysLimit, + /// The given [`AccountHash`](crate::account::AccountHash) is already associated with the given + /// account. + DuplicateKey, + /// Caller doesn't have sufficient permissions to perform the given action. + PermissionDenied, + /// The given [`AccountHash`](crate::account::AccountHash) is not associated with the given + /// account. + MissingKey, + /// Removing/updating the given associated [`AccountHash`](crate::account::AccountHash) would + /// cause the total [`Weight`](crate::account::Weight) of all remaining `AccountHash`s to + /// fall below one of the action thresholds for the given account. + ThresholdViolation, + /// Setting the key-management threshold to a value lower than the deployment threshold is + /// disallowed. + KeyManagementThreshold, + /// Setting the deployment threshold to a value greater than any other threshold is disallowed. + DeploymentThreshold, + /// Setting a threshold to a value greater than the total weight of associated keys is + /// disallowed. + InsufficientTotalWeight, + /// The given `u32` doesn't map to a [`SystemContractType`](crate::SystemContractType). + InvalidSystemContract, + /// Failed to create a new purse. + PurseNotCreated, + /// An unhandled value, likely representing a bug in the code. + Unhandled, + /// The provided buffer is too small to complete an operation. + BufferTooSmall, + /// No data available in the host buffer. + HostBufferEmpty, + /// The host buffer has been set to a value and should be consumed first by a read operation. + HostBufferFull, + /// Could not lay out an array in memory + AllocLayout, + /// Contract header errors. + ContractHeader(u8), + /// Error specific to Mint contract. + Mint(u8), + /// Error specific to Proof of Stake contract. + ProofOfStake(u8), + /// User-specified error code. The internal `u16` value is added to `u16::MAX as u32 + 1` when + /// an `Error::User` is converted to a `u32`. + User(u16), +} + +impl From for ApiError { + fn from(error: bytesrepr::Error) -> Self { + match error { + bytesrepr::Error::EarlyEndOfStream => ApiError::EarlyEndOfStream, + bytesrepr::Error::Formatting => ApiError::Formatting, + bytesrepr::Error::LeftOverBytes => ApiError::LeftOverBytes, + bytesrepr::Error::OutOfMemory => ApiError::OutOfMemory, + } + } +} + +impl From for ApiError { + fn from(error: AddKeyFailure) -> Self { + match error { + AddKeyFailure::MaxKeysLimit => ApiError::MaxKeysLimit, + AddKeyFailure::DuplicateKey => ApiError::DuplicateKey, + AddKeyFailure::PermissionDenied => ApiError::PermissionDenied, + } + } +} + +impl From for ApiError { + fn from(error: UpdateKeyFailure) -> Self { + match error { + UpdateKeyFailure::MissingKey => ApiError::MissingKey, + UpdateKeyFailure::PermissionDenied => ApiError::PermissionDenied, + UpdateKeyFailure::ThresholdViolation => ApiError::ThresholdViolation, + } + } +} + +impl From for ApiError { + fn from(error: RemoveKeyFailure) -> Self { + match error { + RemoveKeyFailure::MissingKey => ApiError::MissingKey, + RemoveKeyFailure::PermissionDenied => ApiError::PermissionDenied, + RemoveKeyFailure::ThresholdViolation => ApiError::ThresholdViolation, + } + } +} + +impl From for ApiError { + fn from(error: SetThresholdFailure) -> Self { + match error { + SetThresholdFailure::KeyManagementThreshold => ApiError::KeyManagementThreshold, + SetThresholdFailure::DeploymentThreshold => ApiError::DeploymentThreshold, + SetThresholdFailure::PermissionDeniedError => ApiError::PermissionDenied, + SetThresholdFailure::InsufficientTotalWeight => ApiError::InsufficientTotalWeight, + } + } +} + +impl From for ApiError { + fn from(error: CLValueError) -> Self { + match error { + CLValueError::Serialization(bytesrepr_error) => bytesrepr_error.into(), + CLValueError::Type(_) => ApiError::CLTypeMismatch, + } + } +} + +impl From for ApiError { + fn from(error: contracts::Error) -> Self { + ApiError::ContractHeader(error as u8) + } +} + +// This conversion is not intended to be used by third party crates. +#[doc(hidden)] +impl From for ApiError { + fn from(_error: TryFromIntError) -> Self { + ApiError::Unhandled + } +} + +impl From for ApiError { + fn from(_error: TryFromSliceForAccountHashError) -> Self { + ApiError::Deserialize + } +} + +impl From for ApiError { + fn from(error: mint::Error) -> Self { + ApiError::Mint(error as u8) + } +} + +impl From for ApiError { + fn from(error: pos::Error) -> Self { + ApiError::ProofOfStake(error as u8) + } +} + +impl From for u32 { + fn from(error: ApiError) -> Self { + match error { + ApiError::None => 1, + ApiError::MissingArgument => 2, + ApiError::InvalidArgument => 3, + ApiError::Deserialize => 4, + ApiError::Read => 5, + ApiError::ValueNotFound => 6, + ApiError::ContractNotFound => 7, + ApiError::GetKey => 8, + ApiError::UnexpectedKeyVariant => 9, + ApiError::UnexpectedContractRefVariant => 10, + ApiError::InvalidPurseName => 11, + ApiError::InvalidPurse => 12, + ApiError::UpgradeContractAtURef => 13, + ApiError::Transfer => 14, + ApiError::NoAccessRights => 15, + ApiError::CLTypeMismatch => 16, + ApiError::EarlyEndOfStream => 17, + ApiError::Formatting => 18, + ApiError::LeftOverBytes => 19, + ApiError::OutOfMemory => 20, + ApiError::MaxKeysLimit => 21, + ApiError::DuplicateKey => 22, + ApiError::PermissionDenied => 23, + ApiError::MissingKey => 24, + ApiError::ThresholdViolation => 25, + ApiError::KeyManagementThreshold => 26, + ApiError::DeploymentThreshold => 27, + ApiError::InsufficientTotalWeight => 28, + ApiError::InvalidSystemContract => 29, + ApiError::PurseNotCreated => 30, + ApiError::Unhandled => 31, + ApiError::BufferTooSmall => 32, + ApiError::HostBufferEmpty => 33, + ApiError::HostBufferFull => 34, + ApiError::AllocLayout => 35, + ApiError::ContractHeader(value) => HEADER_ERROR_OFFSET + u32::from(value), + ApiError::Mint(value) => MINT_ERROR_OFFSET + u32::from(value), + ApiError::ProofOfStake(value) => POS_ERROR_OFFSET + u32::from(value), + ApiError::User(value) => RESERVED_ERROR_MAX + 1 + u32::from(value), + } + } +} + +impl From for ApiError { + fn from(value: u32) -> ApiError { + match value { + 1 => ApiError::None, + 2 => ApiError::MissingArgument, + 3 => ApiError::InvalidArgument, + 4 => ApiError::Deserialize, + 5 => ApiError::Read, + 6 => ApiError::ValueNotFound, + 7 => ApiError::ContractNotFound, + 8 => ApiError::GetKey, + 9 => ApiError::UnexpectedKeyVariant, + 10 => ApiError::UnexpectedContractRefVariant, + 11 => ApiError::InvalidPurseName, + 12 => ApiError::InvalidPurse, + 13 => ApiError::UpgradeContractAtURef, + 14 => ApiError::Transfer, + 15 => ApiError::NoAccessRights, + 16 => ApiError::CLTypeMismatch, + 17 => ApiError::EarlyEndOfStream, + 18 => ApiError::Formatting, + 19 => ApiError::LeftOverBytes, + 20 => ApiError::OutOfMemory, + 21 => ApiError::MaxKeysLimit, + 22 => ApiError::DuplicateKey, + 23 => ApiError::PermissionDenied, + 24 => ApiError::MissingKey, + 25 => ApiError::ThresholdViolation, + 26 => ApiError::KeyManagementThreshold, + 27 => ApiError::DeploymentThreshold, + 28 => ApiError::InsufficientTotalWeight, + 29 => ApiError::InvalidSystemContract, + 30 => ApiError::PurseNotCreated, + 31 => ApiError::Unhandled, + 32 => ApiError::BufferTooSmall, + 33 => ApiError::HostBufferEmpty, + 34 => ApiError::HostBufferFull, + 35 => ApiError::AllocLayout, + USER_ERROR_MIN..=USER_ERROR_MAX => ApiError::User(value as u16), + POS_ERROR_MIN..=POS_ERROR_MAX => ApiError::ProofOfStake(value as u8), + MINT_ERROR_MIN..=MINT_ERROR_MAX => ApiError::Mint(value as u8), + HEADER_ERROR_MIN..=HEADER_ERROR_MAX => ApiError::ContractHeader(value as u8), + _ => ApiError::Unhandled, + } + } +} + +impl Debug for ApiError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + ApiError::None => write!(f, "ApiError::None")?, + ApiError::MissingArgument => write!(f, "ApiError::MissingArgument")?, + ApiError::InvalidArgument => write!(f, "ApiError::InvalidArgument")?, + ApiError::Deserialize => write!(f, "ApiError::Deserialize")?, + ApiError::Read => write!(f, "ApiError::Read")?, + ApiError::ValueNotFound => write!(f, "ApiError::ValueNotFound")?, + ApiError::ContractNotFound => write!(f, "ApiError::ContractNotFound")?, + ApiError::GetKey => write!(f, "ApiError::GetKey")?, + ApiError::UnexpectedKeyVariant => write!(f, "ApiError::UnexpectedKeyVariant")?, + ApiError::UnexpectedContractRefVariant => { + write!(f, "ApiError::UnexpectedContractRefVariant")? + } + ApiError::InvalidPurseName => write!(f, "ApiError::InvalidPurseName")?, + ApiError::InvalidPurse => write!(f, "ApiError::InvalidPurse")?, + ApiError::UpgradeContractAtURef => write!(f, "ApiError::UpgradeContractAtURef")?, + ApiError::Transfer => write!(f, "ApiError::Transfer")?, + ApiError::NoAccessRights => write!(f, "ApiError::NoAccessRights")?, + ApiError::CLTypeMismatch => write!(f, "ApiError::CLTypeMismatch")?, + ApiError::EarlyEndOfStream => write!(f, "ApiError::EarlyEndOfStream")?, + ApiError::Formatting => write!(f, "ApiError::Formatting")?, + ApiError::LeftOverBytes => write!(f, "ApiError::LeftOverBytes")?, + ApiError::OutOfMemory => write!(f, "ApiError::OutOfMemory")?, + ApiError::MaxKeysLimit => write!(f, "ApiError::MaxKeysLimit")?, + ApiError::DuplicateKey => write!(f, "ApiError::DuplicateKey")?, + ApiError::PermissionDenied => write!(f, "ApiError::PermissionDenied")?, + ApiError::MissingKey => write!(f, "ApiError::MissingKey")?, + ApiError::ThresholdViolation => write!(f, "ApiError::ThresholdViolation")?, + ApiError::KeyManagementThreshold => write!(f, "ApiError::KeyManagementThreshold")?, + ApiError::DeploymentThreshold => write!(f, "ApiError::DeploymentThreshold")?, + ApiError::InsufficientTotalWeight => write!(f, "ApiError::InsufficientTotalWeight")?, + ApiError::InvalidSystemContract => write!(f, "ApiError::InvalidSystemContract")?, + ApiError::PurseNotCreated => write!(f, "ApiError::PurseNotCreated")?, + ApiError::Unhandled => write!(f, "ApiError::Unhandled")?, + ApiError::BufferTooSmall => write!(f, "ApiError::BufferTooSmall")?, + ApiError::HostBufferEmpty => write!(f, "ApiError::HostBufferEmpty")?, + ApiError::HostBufferFull => write!(f, "ApiError::HostBufferFull")?, + ApiError::AllocLayout => write!(f, "ApiError::AllocLayout")?, + ApiError::ContractHeader(value) => write!(f, "ApiError::ContractHeader({})", value)?, + ApiError::Mint(value) => write!(f, "ApiError::Mint({})", value)?, + ApiError::ProofOfStake(value) => write!(f, "ApiError::ProofOfStake({})", value)?, + ApiError::User(value) => write!(f, "ApiError::User({})", value)?, + } + write!(f, " [{}]", u32::from(*self)) + } +} + +impl fmt::Display for ApiError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ApiError::User(value) => write!(f, "User error: {}", value), + ApiError::ContractHeader(value) => write!(f, "Contract header error: {}", value), + ApiError::Mint(value) => write!(f, "Mint error: {}", value), + ApiError::ProofOfStake(value) => write!(f, "PoS error: {}", value), + _ => ::fmt(&self, f), + } + } +} + +// This function is not intended to be used by third party crates. +#[doc(hidden)] +pub fn i32_from(result: Result<(), ApiError>) -> i32 { + match result { + Ok(()) => 0, + Err(error) => u32::from(error) as i32, + } +} + +/// Converts an `i32` to a `Result<(), ApiError>`, where `0` represents `Ok(())`, and all other +/// inputs are mapped to `Err(ApiError::)`. The full list of mappings can be found in the +/// [docs for `ApiError`](ApiError#mappings). +pub fn result_from(value: i32) -> Result<(), ApiError> { + match value { + 0 => Ok(()), + _ => Err(ApiError::from(value as u32)), + } +} + +#[cfg(test)] +mod tests { + use std::{i32, u16, u8}; + + use super::*; + + fn round_trip(result: Result<(), ApiError>) { + let code = i32_from(result); + assert_eq!(result, result_from(code)); + } + + #[test] + fn error_values() { + assert_eq!(65_024_u32, ApiError::Mint(0).into()); // MINT_ERROR_OFFSET == 65,024 + assert_eq!(65_279_u32, ApiError::Mint(u8::MAX).into()); + assert_eq!(65_280_u32, ApiError::ProofOfStake(0).into()); // POS_ERROR_OFFSET == 65,280 + assert_eq!(65_535_u32, ApiError::ProofOfStake(u8::MAX).into()); + assert_eq!(65_536_u32, ApiError::User(0).into()); // u16::MAX + 1 + assert_eq!(131_071_u32, ApiError::User(u16::MAX).into()); // 2 * u16::MAX + 1 + } + + #[test] + fn error_descriptions() { + assert_eq!("ApiError::GetKey [8]", &format!("{:?}", ApiError::GetKey)); + assert_eq!("ApiError::GetKey [8]", &format!("{}", ApiError::GetKey)); + + assert_eq!( + "ApiError::ContractHeader(0) [64768]", + &format!("{:?}", ApiError::ContractHeader(0)) + ); + assert_eq!( + "Contract header error: 0", + &format!("{}", ApiError::ContractHeader(0)) + ); + assert_eq!( + "Contract header error: 255", + &format!("{}", ApiError::ContractHeader(u8::MAX)) + ); + + assert_eq!( + "ApiError::Mint(0) [65024]", + &format!("{:?}", ApiError::Mint(0)) + ); + assert_eq!("Mint error: 0", &format!("{}", ApiError::Mint(0))); + assert_eq!("Mint error: 255", &format!("{}", ApiError::Mint(u8::MAX))); + assert_eq!( + "ApiError::ProofOfStake(0) [65280]", + &format!("{:?}", ApiError::ProofOfStake(0)) + ); + assert_eq!("PoS error: 0", &format!("{}", ApiError::ProofOfStake(0))); + assert_eq!( + "ApiError::ProofOfStake(255) [65535]", + &format!("{:?}", ApiError::ProofOfStake(u8::MAX)) + ); + assert_eq!( + "ApiError::User(0) [65536]", + &format!("{:?}", ApiError::User(0)) + ); + assert_eq!("User error: 0", &format!("{}", ApiError::User(0))); + assert_eq!( + "ApiError::User(65535) [131071]", + &format!("{:?}", ApiError::User(u16::MAX)) + ); + assert_eq!( + "User error: 65535", + &format!("{}", ApiError::User(u16::MAX)) + ); + } + + #[test] + fn error_edge_cases() { + assert_eq!(Err(ApiError::Unhandled), result_from(i32::MAX)); + assert_eq!( + Err(ApiError::ContractHeader(255)), + result_from(MINT_ERROR_OFFSET as i32 - 1) + ); + assert_eq!(Err(ApiError::Unhandled), result_from(-1)); + assert_eq!(Err(ApiError::Unhandled), result_from(i32::MIN)); + } + + #[test] + fn error_round_trips() { + round_trip(Ok(())); + round_trip(Err(ApiError::None)); + round_trip(Err(ApiError::MissingArgument)); + round_trip(Err(ApiError::InvalidArgument)); + round_trip(Err(ApiError::Deserialize)); + round_trip(Err(ApiError::Read)); + round_trip(Err(ApiError::ValueNotFound)); + round_trip(Err(ApiError::ContractNotFound)); + round_trip(Err(ApiError::GetKey)); + round_trip(Err(ApiError::UnexpectedKeyVariant)); + round_trip(Err(ApiError::UnexpectedContractRefVariant)); + round_trip(Err(ApiError::InvalidPurseName)); + round_trip(Err(ApiError::InvalidPurse)); + round_trip(Err(ApiError::UpgradeContractAtURef)); + round_trip(Err(ApiError::Transfer)); + round_trip(Err(ApiError::NoAccessRights)); + round_trip(Err(ApiError::CLTypeMismatch)); + round_trip(Err(ApiError::EarlyEndOfStream)); + round_trip(Err(ApiError::Formatting)); + round_trip(Err(ApiError::LeftOverBytes)); + round_trip(Err(ApiError::OutOfMemory)); + round_trip(Err(ApiError::MaxKeysLimit)); + round_trip(Err(ApiError::DuplicateKey)); + round_trip(Err(ApiError::PermissionDenied)); + round_trip(Err(ApiError::MissingKey)); + round_trip(Err(ApiError::ThresholdViolation)); + round_trip(Err(ApiError::KeyManagementThreshold)); + round_trip(Err(ApiError::DeploymentThreshold)); + round_trip(Err(ApiError::InsufficientTotalWeight)); + round_trip(Err(ApiError::InvalidSystemContract)); + round_trip(Err(ApiError::PurseNotCreated)); + round_trip(Err(ApiError::Unhandled)); + round_trip(Err(ApiError::BufferTooSmall)); + round_trip(Err(ApiError::HostBufferEmpty)); + round_trip(Err(ApiError::HostBufferFull)); + round_trip(Err(ApiError::AllocLayout)); + round_trip(Err(ApiError::ContractHeader(0))); + round_trip(Err(ApiError::ContractHeader(u8::MAX))); + round_trip(Err(ApiError::Mint(0))); + round_trip(Err(ApiError::Mint(u8::MAX))); + round_trip(Err(ApiError::ProofOfStake(0))); + round_trip(Err(ApiError::ProofOfStake(u8::MAX))); + round_trip(Err(ApiError::User(0))); + round_trip(Err(ApiError::User(u16::MAX))); + } +} diff --git a/types/src/block_time.rs b/types/src/block_time.rs new file mode 100644 index 0000000000..f250136e85 --- /dev/null +++ b/types/src/block_time.rs @@ -0,0 +1,46 @@ +use alloc::vec::Vec; + +use crate::bytesrepr::{Error, FromBytes, ToBytes, U64_SERIALIZED_LENGTH}; + +/// The number of bytes in a serialized [`BlockTime`]. +pub const BLOCKTIME_SERIALIZED_LENGTH: usize = U64_SERIALIZED_LENGTH; + +/// A newtype wrapping a [`u64`] which represents the block time. +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd)] +pub struct BlockTime(u64); + +impl BlockTime { + /// Constructs a `BlockTime`. + pub fn new(value: u64) -> Self { + BlockTime(value) + } + + /// Saturating integer subtraction. Computes `self - other`, saturating at `0` instead of + /// overflowing. + pub fn saturating_sub(self, other: BlockTime) -> Self { + BlockTime(self.0.saturating_sub(other.0)) + } +} + +impl Into for BlockTime { + fn into(self) -> u64 { + self.0 + } +} + +impl ToBytes for BlockTime { + fn to_bytes(&self) -> Result, Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + BLOCKTIME_SERIALIZED_LENGTH + } +} + +impl FromBytes for BlockTime { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (time, rem) = FromBytes::from_bytes(bytes)?; + Ok((BlockTime::new(time), rem)) + } +} diff --git a/types/src/bytesrepr.rs b/types/src/bytesrepr.rs new file mode 100644 index 0000000000..5103e70634 --- /dev/null +++ b/types/src/bytesrepr.rs @@ -0,0 +1,1089 @@ +//! Contains serialization and deserialization code for types used throughout the system. + +// Can be removed once https://github.com/rust-lang/rustfmt/issues/3362 is resolved. +#[rustfmt::skip] +use alloc::vec; +#[cfg(feature = "no-unstable-features")] +use alloc::alloc::{alloc, Layout}; +#[cfg(not(feature = "no-unstable-features"))] +use alloc::collections::TryReserveError; +use alloc::{ + collections::{BTreeMap, BTreeSet}, + string::String, + vec::Vec, +}; +use core::mem::{self, MaybeUninit}; +#[cfg(feature = "no-unstable-features")] +use core::ptr::NonNull; + +use failure::Fail; + +/// The number of bytes in a serialized `()`. +pub const UNIT_SERIALIZED_LENGTH: usize = 0; +/// The number of bytes in a serialized `bool`. +pub const BOOL_SERIALIZED_LENGTH: usize = 1; +/// The number of bytes in a serialized `i32`. +pub const I32_SERIALIZED_LENGTH: usize = mem::size_of::(); +/// The number of bytes in a serialized `i64`. +pub const I64_SERIALIZED_LENGTH: usize = mem::size_of::(); +/// The number of bytes in a serialized `u8`. +pub const U8_SERIALIZED_LENGTH: usize = mem::size_of::(); +/// The number of bytes in a serialized `u16`. +pub const U16_SERIALIZED_LENGTH: usize = mem::size_of::(); +/// The number of bytes in a serialized `u32`. +pub const U32_SERIALIZED_LENGTH: usize = mem::size_of::(); +/// The number of bytes in a serialized `u64`. +pub const U64_SERIALIZED_LENGTH: usize = mem::size_of::(); +/// The number of bytes in a serialized [`U128`](crate::U128). +pub const U128_SERIALIZED_LENGTH: usize = mem::size_of::(); +/// The number of bytes in a serialized [`U256`](crate::U256). +pub const U256_SERIALIZED_LENGTH: usize = U128_SERIALIZED_LENGTH * 2; +/// The number of bytes in a serialized [`U512`](crate::U512). +pub const U512_SERIALIZED_LENGTH: usize = U256_SERIALIZED_LENGTH * 2; + +/// A type which can be serialized to a `Vec`. +pub trait ToBytes { + /// Serializes `&self` to a `Vec`. + fn to_bytes(&self) -> Result, Error>; + /// Consumes `self` and serializes to a `Vec`. + fn into_bytes(self) -> Result, Error> + where + Self: Sized, + { + self.to_bytes() + } + /// Returns the length of the `Vec` which would be returned from a successful call to + /// `to_bytes()` or `into_bytes()`. The data is not actually serialized, so this call is + /// relatively cheap. + fn serialized_length(&self) -> usize; +} + +/// A type which can be deserialized from a `Vec`. +pub trait FromBytes: Sized { + /// Deserializes the slice into `Self`. + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error>; + /// Deserializes the `Vec` into `Self`. + fn from_vec(bytes: Vec) -> Result<(Self, Vec), Error> { + Self::from_bytes(bytes.as_slice()).map(|(x, remainder)| (x, Vec::from(remainder))) + } +} + +/// Returns a `Vec` initialized with sufficient capacity to hold `to_be_serialized` after +/// serialization. +pub fn unchecked_allocate_buffer(to_be_serialized: &T) -> Vec { + let serialized_length = to_be_serialized.serialized_length(); + Vec::with_capacity(serialized_length) +} + +/// Returns a `Vec` initialized with sufficient capacity to hold `to_be_serialized` after +/// serialization, or an error if the capacity would exceed `u32::max_value()`. +pub fn allocate_buffer(to_be_serialized: &T) -> Result, Error> { + let serialized_length = to_be_serialized.serialized_length(); + if serialized_length > u32::max_value() as usize { + return Err(Error::OutOfMemory); + } + Ok(Vec::with_capacity(serialized_length)) +} + +/// Serialization and deserialization errors. +#[derive(Debug, Fail, PartialEq, Eq, Clone)] +#[repr(u8)] +pub enum Error { + /// Early end of stream while deserializing. + #[fail(display = "Deserialization error: early end of stream")] + EarlyEndOfStream = 0, + /// Formatting error while deserializing. + #[fail(display = "Deserialization error: formatting")] + Formatting, + /// Not all input bytes were consumed in [`deserialize`]. + #[fail(display = "Deserialization error: left-over bytes")] + LeftOverBytes, + /// Out of memory error. + #[fail(display = "Serialization error: out of memory")] + OutOfMemory, +} + +#[cfg(not(feature = "no-unstable-features"))] +impl From for Error { + fn from(_: TryReserveError) -> Error { + Error::OutOfMemory + } +} + +/// Deserializes `bytes` into an instance of `T`. +/// +/// Returns an error if the bytes cannot be deserialized into `T` or if not all of the input bytes +/// are consumed in the operation. +pub fn deserialize(bytes: Vec) -> Result { + let (t, remainder) = T::from_vec(bytes)?; + if remainder.is_empty() { + Ok(t) + } else { + Err(Error::LeftOverBytes) + } +} + +/// Serializes `t` into a `Vec`. +pub fn serialize(t: impl ToBytes) -> Result, Error> { + t.into_bytes() +} + +pub(crate) fn safe_split_at(bytes: &[u8], n: usize) -> Result<(&[u8], &[u8]), Error> { + if n > bytes.len() { + Err(Error::EarlyEndOfStream) + } else { + Ok(bytes.split_at(n)) + } +} + +impl ToBytes for () { + fn to_bytes(&self) -> Result, Error> { + Ok(Vec::new()) + } + + fn serialized_length(&self) -> usize { + UNIT_SERIALIZED_LENGTH + } +} + +impl FromBytes for () { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + Ok(((), bytes)) + } +} + +impl ToBytes for bool { + fn to_bytes(&self) -> Result, Error> { + u8::from(*self).to_bytes() + } + + fn serialized_length(&self) -> usize { + BOOL_SERIALIZED_LENGTH + } +} + +impl FromBytes for bool { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + match bytes.split_first() { + None => Err(Error::EarlyEndOfStream), + Some((byte, rem)) => match byte { + 1 => Ok((true, rem)), + 0 => Ok((false, rem)), + _ => Err(Error::Formatting), + }, + } + } +} + +impl ToBytes for u8 { + fn to_bytes(&self) -> Result, Error> { + Ok(vec![*self]) + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + } +} + +impl FromBytes for u8 { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + match bytes.split_first() { + None => Err(Error::EarlyEndOfStream), + Some((byte, rem)) => Ok((*byte, rem)), + } + } +} + +impl ToBytes for i32 { + fn to_bytes(&self) -> Result, Error> { + Ok(self.to_le_bytes().to_vec()) + } + + fn serialized_length(&self) -> usize { + I32_SERIALIZED_LENGTH + } +} + +impl FromBytes for i32 { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let mut result = [0u8; I32_SERIALIZED_LENGTH]; + let (bytes, remainder) = safe_split_at(bytes, I32_SERIALIZED_LENGTH)?; + result.copy_from_slice(bytes); + Ok((::from_le_bytes(result), remainder)) + } +} + +impl ToBytes for i64 { + fn to_bytes(&self) -> Result, Error> { + Ok(self.to_le_bytes().to_vec()) + } + + fn serialized_length(&self) -> usize { + I64_SERIALIZED_LENGTH + } +} + +impl FromBytes for i64 { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let mut result = [0u8; I64_SERIALIZED_LENGTH]; + let (bytes, remainder) = safe_split_at(bytes, I64_SERIALIZED_LENGTH)?; + result.copy_from_slice(bytes); + Ok((::from_le_bytes(result), remainder)) + } +} + +impl ToBytes for u16 { + fn to_bytes(&self) -> Result, Error> { + Ok(self.to_le_bytes().to_vec()) + } + + fn serialized_length(&self) -> usize { + U16_SERIALIZED_LENGTH + } +} + +impl FromBytes for u16 { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let mut result = [0u8; U16_SERIALIZED_LENGTH]; + let (bytes, remainder) = safe_split_at(bytes, U16_SERIALIZED_LENGTH)?; + result.copy_from_slice(bytes); + Ok((::from_le_bytes(result), remainder)) + } +} + +impl ToBytes for u32 { + fn to_bytes(&self) -> Result, Error> { + Ok(self.to_le_bytes().to_vec()) + } + + fn serialized_length(&self) -> usize { + U32_SERIALIZED_LENGTH + } +} + +impl FromBytes for u32 { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let mut result = [0u8; U32_SERIALIZED_LENGTH]; + let (bytes, remainder) = safe_split_at(bytes, U32_SERIALIZED_LENGTH)?; + result.copy_from_slice(bytes); + Ok((::from_le_bytes(result), remainder)) + } +} + +impl ToBytes for u64 { + fn to_bytes(&self) -> Result, Error> { + Ok(self.to_le_bytes().to_vec()) + } + + fn serialized_length(&self) -> usize { + U64_SERIALIZED_LENGTH + } +} + +impl FromBytes for u64 { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let mut result = [0u8; U64_SERIALIZED_LENGTH]; + let (bytes, remainder) = safe_split_at(bytes, U64_SERIALIZED_LENGTH)?; + result.copy_from_slice(bytes); + Ok((::from_le_bytes(result), remainder)) + } +} + +impl ToBytes for String { + fn to_bytes(&self) -> Result, Error> { + self.as_str().to_bytes() + } + + fn serialized_length(&self) -> usize { + self.as_str().serialized_length() + } +} + +impl FromBytes for String { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (str_bytes, rem): (Vec, &[u8]) = FromBytes::from_bytes(bytes)?; + let result = String::from_utf8(str_bytes).map_err(|_| Error::Formatting)?; + Ok((result, rem)) + } +} + +#[allow(clippy::ptr_arg)] +fn vec_to_bytes(vec: &Vec) -> Result, Error> { + let mut result = allocate_buffer(vec)?; + result.append(&mut (vec.len() as u32).to_bytes()?); + + for item in vec.iter() { + result.append(&mut item.to_bytes()?); + } + + Ok(result) +} + +fn vec_into_bytes(vec: Vec) -> Result, Error> { + let mut result = allocate_buffer(&vec)?; + result.append(&mut (vec.len() as u32).to_bytes()?); + + for item in vec { + result.append(&mut item.into_bytes()?); + } + + Ok(result) +} + +fn vec_serialized_length(vec: &[T]) -> usize { + U32_SERIALIZED_LENGTH + vec.iter().map(ToBytes::serialized_length).sum::() +} + +#[cfg(feature = "no-unstable-features")] +impl ToBytes for Vec { + fn to_bytes(&self) -> Result, Error> { + vec_to_bytes(self) + } + + fn into_bytes(self) -> Result, Error> { + vec_into_bytes(self) + } + + fn serialized_length(&self) -> usize { + vec_serialized_length(self) + } +} + +#[cfg(not(feature = "no-unstable-features"))] +impl ToBytes for Vec { + default fn to_bytes(&self) -> Result, Error> { + vec_to_bytes(self) + } + + default fn into_bytes(self) -> Result, Error> { + vec_into_bytes(self) + } + + default fn serialized_length(&self) -> usize { + vec_serialized_length(self) + } +} + +#[cfg(feature = "no-unstable-features")] +fn try_vec_with_capacity(capacity: usize) -> Result, Error> { + // see https://doc.rust-lang.org/src/alloc/raw_vec.rs.html#75-98 + let elem_size = mem::size_of::(); + let alloc_size = capacity + .checked_mul(elem_size) + .ok_or_else(|| Error::OutOfMemory)?; + + let ptr = if alloc_size == 0 { + NonNull::::dangling() + } else { + let align = mem::align_of::(); + let layout = Layout::from_size_align(alloc_size, align).unwrap(); + let raw_ptr = unsafe { alloc(layout) }; + let non_null_ptr = NonNull::::new(raw_ptr).ok_or_else(|| Error::OutOfMemory)?; + non_null_ptr.cast() + }; + unsafe { Ok(Vec::from_raw_parts(ptr.as_ptr(), 0, capacity)) } +} + +#[cfg(not(feature = "no-unstable-features"))] +fn try_vec_with_capacity(capacity: usize) -> Result, Error> { + let mut result: Vec = Vec::new(); + result.try_reserve_exact(capacity)?; + Ok(result) +} + +fn vec_from_bytes(bytes: &[u8]) -> Result<(Vec, &[u8]), Error> { + let (count, mut stream) = u32::from_bytes(bytes)?; + + let mut result = try_vec_with_capacity(count as usize)?; + for _ in 0..count { + let (value, remainder) = T::from_bytes(stream)?; + result.push(value); + stream = remainder; + } + + Ok((result, stream)) +} + +fn vec_from_vec(bytes: Vec) -> Result<(Vec, Vec), Error> { + Vec::::from_bytes(bytes.as_slice()).map(|(x, remainder)| (x, Vec::from(remainder))) +} + +#[cfg(feature = "no-unstable-features")] +impl FromBytes for Vec { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + vec_from_bytes(bytes) + } + + fn from_vec(bytes: Vec) -> Result<(Self, Vec), Error> { + vec_from_vec(bytes) + } +} + +#[cfg(not(feature = "no-unstable-features"))] +impl FromBytes for Vec { + default fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + vec_from_bytes(bytes) + } + + default fn from_vec(bytes: Vec) -> Result<(Self, Vec), Error> { + vec_from_vec(bytes) + } +} + +#[cfg(not(feature = "no-unstable-features"))] +impl ToBytes for Vec { + fn to_bytes(&self) -> Result, Error> { + let mut result = allocate_buffer(self)?; + result.append(&mut (self.len() as u32).to_bytes()?); + result.extend(self); + Ok(result) + } + + fn into_bytes(mut self) -> Result, Error> { + let mut result = allocate_buffer(&self)?; + result.append(&mut (self.len() as u32).to_bytes()?); + result.append(&mut self); + Ok(result) + } + + fn serialized_length(&self) -> usize { + U32_SERIALIZED_LENGTH + self.len() + } +} + +#[cfg(not(feature = "no-unstable-features"))] +impl FromBytes for Vec { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (size, remainder) = u32::from_bytes(bytes)?; + let (result, remainder) = safe_split_at(remainder, size as usize)?; + Ok((result.to_vec(), remainder)) + } + + fn from_vec(bytes: Vec) -> Result<(Self, Vec), Error> { + let (size, mut stream) = u32::from_vec(bytes)?; + + if size as usize > stream.len() { + Err(Error::EarlyEndOfStream) + } else { + let remainder = stream.split_off(size as usize); + Ok((stream, remainder)) + } + } +} + +macro_rules! impl_to_from_bytes_for_array { + ($($N:literal)+) => { + $( + #[cfg(feature = "no-unstable-features")] + impl ToBytes for [T; $N] { + fn to_bytes(&self) -> Result, Error> { + let mut result = allocate_buffer(self)?; + for item in self.iter() { + result.append(&mut item.to_bytes()?); + } + Ok(result) + } + + fn serialized_length(&self) -> usize { + self.iter().map(ToBytes::serialized_length).sum::() + } + } + + #[cfg(feature = "no-unstable-features")] + impl FromBytes for [T; $N] { + fn from_bytes(mut bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let mut result: MaybeUninit<[T; $N]> = MaybeUninit::uninit(); + let result_ptr = result.as_mut_ptr() as *mut T; + unsafe { + for i in 0..$N { + let (t, remainder) = match T::from_bytes(bytes) { + Ok(success) => success, + Err(error) => { + for j in 0..i { + result_ptr.add(j).drop_in_place(); + } + return Err(error); + } + }; + result_ptr.add(i).write(t); + bytes = remainder; + } + Ok((result.assume_init(), bytes)) + } + } + } + + #[cfg(not(feature = "no-unstable-features"))] + impl ToBytes for [T; $N] { + default fn to_bytes(&self) -> Result, Error> { + let mut result = allocate_buffer(self)?; + for item in self.iter() { + result.append(&mut item.to_bytes()?); + } + Ok(result) + } + + default fn serialized_length(&self) -> usize { + self.iter().map(ToBytes::serialized_length).sum::() + } + } + + #[cfg(not(feature = "no-unstable-features"))] + impl FromBytes for [T; $N] { + default fn from_bytes(mut bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let mut result: MaybeUninit<[T; $N]> = MaybeUninit::uninit(); + let result_ptr = result.as_mut_ptr() as *mut T; + unsafe { + #[allow(clippy::reversed_empty_ranges)] + for i in 0..$N { + let (t, remainder) = match T::from_bytes(bytes) { + Ok(success) => success, + Err(error) => { + for j in 0..i { + result_ptr.add(j).drop_in_place(); + } + return Err(error); + } + }; + result_ptr.add(i).write(t); + bytes = remainder; + } + Ok((result.assume_init(), bytes)) + } + } + } + )+ + } +} + +impl_to_from_bytes_for_array! { + 0 1 2 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 28 29 + 30 31 32 + 64 128 256 512 +} + +#[cfg(not(feature = "no-unstable-features"))] +macro_rules! impl_to_from_bytes_for_byte_array { + ($($len:expr)+) => { + $( + impl ToBytes for [u8; $len] { + fn to_bytes(&self) -> Result, Error> { + Ok(self.to_vec()) + } + + fn serialized_length(&self) -> usize { $len } + } + + impl FromBytes for [u8; $len] { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (bytes, rem) = safe_split_at(bytes, $len)?; + let mut result = [0u8; $len]; + result.copy_from_slice(bytes); + Ok((result, rem)) + } + } + )+ + } +} + +#[cfg(not(feature = "no-unstable-features"))] +impl_to_from_bytes_for_byte_array! { + 0 1 2 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 28 29 + 30 31 32 + 64 128 256 512 +} + +impl ToBytes for BTreeSet { + fn to_bytes(&self) -> Result, Error> { + let mut result = allocate_buffer(self)?; + + let num_keys = self.len() as u32; + result.append(&mut num_keys.to_bytes()?); + + for value in self.iter() { + result.append(&mut value.to_bytes()?); + } + + Ok(result) + } + + fn serialized_length(&self) -> usize { + U32_SERIALIZED_LENGTH + self.iter().map(|v| v.serialized_length()).sum::() + } +} + +impl FromBytes for BTreeSet { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (num_keys, mut stream) = u32::from_bytes(bytes)?; + let mut result = BTreeSet::new(); + for _ in 0..num_keys { + let (v, rem) = V::from_bytes(stream)?; + result.insert(v); + stream = rem; + } + Ok((result, stream)) + } +} + +impl ToBytes for BTreeMap +where + K: ToBytes, + V: ToBytes, +{ + fn to_bytes(&self) -> Result, Error> { + let mut result = allocate_buffer(self)?; + + let num_keys = self.len() as u32; + result.append(&mut num_keys.to_bytes()?); + + for (key, value) in self.iter() { + result.append(&mut key.to_bytes()?); + result.append(&mut value.to_bytes()?); + } + + Ok(result) + } + + fn serialized_length(&self) -> usize { + U32_SERIALIZED_LENGTH + + self + .iter() + .map(|(key, value)| key.serialized_length() + value.serialized_length()) + .sum::() + } +} + +impl FromBytes for BTreeMap +where + K: FromBytes + Ord, + V: FromBytes, +{ + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (num_keys, mut stream) = u32::from_bytes(bytes)?; + let mut result = BTreeMap::new(); + for _ in 0..num_keys { + let (k, rem) = K::from_bytes(stream)?; + let (v, rem) = V::from_bytes(rem)?; + result.insert(k, v); + stream = rem; + } + Ok((result, stream)) + } +} + +impl ToBytes for Option { + fn to_bytes(&self) -> Result, Error> { + match self { + None => Ok(vec![0]), + Some(v) => { + let mut result = allocate_buffer(self)?; + result.push(1); + + let mut value = v.to_bytes()?; + result.append(&mut value); + + Ok(result) + } + } + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + + match self { + Some(v) => v.serialized_length(), + None => 0, + } + } +} + +impl FromBytes for Option { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (tag, rem) = u8::from_bytes(bytes)?; + match tag { + 0 => Ok((None, rem)), + 1 => { + let (t, rem) = T::from_bytes(rem)?; + Ok((Some(t), rem)) + } + _ => Err(Error::Formatting), + } + } +} + +impl ToBytes for Result { + fn to_bytes(&self) -> Result, Error> { + let mut result = allocate_buffer(self)?; + let (variant, mut value) = match self { + Err(error) => (0, error.to_bytes()?), + Ok(result) => (1, result.to_bytes()?), + }; + result.push(variant); + result.append(&mut value); + Ok(result) + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + + match self { + Ok(ok) => ok.serialized_length(), + Err(error) => error.serialized_length(), + } + } +} + +impl FromBytes for Result { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (variant, rem) = u8::from_bytes(bytes)?; + match variant { + 0 => { + let (value, rem) = E::from_bytes(rem)?; + Ok((Err(value), rem)) + } + 1 => { + let (value, rem) = T::from_bytes(rem)?; + Ok((Ok(value), rem)) + } + _ => Err(Error::Formatting), + } + } +} + +impl ToBytes for (T1,) { + fn to_bytes(&self) -> Result, Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } +} + +impl FromBytes for (T1,) { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (t1, remainder) = T1::from_bytes(bytes)?; + Ok(((t1,), remainder)) + } +} + +impl ToBytes for (T1, T2) { + fn to_bytes(&self) -> Result, Error> { + let mut result = allocate_buffer(self)?; + result.append(&mut self.0.to_bytes()?); + result.append(&mut self.1.to_bytes()?); + Ok(result) + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + self.1.serialized_length() + } +} + +impl FromBytes for (T1, T2) { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (t1, remainder) = T1::from_bytes(bytes)?; + let (t2, remainder) = T2::from_bytes(remainder)?; + Ok(((t1, t2), remainder)) + } +} + +impl ToBytes for (T1, T2, T3) { + fn to_bytes(&self) -> Result, Error> { + let mut result = allocate_buffer(self)?; + result.append(&mut self.0.to_bytes()?); + result.append(&mut self.1.to_bytes()?); + result.append(&mut self.2.to_bytes()?); + Ok(result) + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + self.1.serialized_length() + self.2.serialized_length() + } +} + +impl FromBytes for (T1, T2, T3) { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (t1, remainder) = T1::from_bytes(bytes)?; + let (t2, remainder) = T2::from_bytes(remainder)?; + let (t3, remainder) = T3::from_bytes(remainder)?; + Ok(((t1, t2, t3), remainder)) + } +} + +impl ToBytes for str { + fn to_bytes(&self) -> Result, Error> { + if self.len() > u32::max_value() as usize - U32_SERIALIZED_LENGTH { + return Err(Error::OutOfMemory); + } + self.as_bytes().to_vec().into_bytes() + } + + fn serialized_length(&self) -> usize { + U32_SERIALIZED_LENGTH + self.as_bytes().len() + } +} + +impl ToBytes for &str { + fn to_bytes(&self) -> Result, Error> { + (*self).to_bytes() + } + + fn serialized_length(&self) -> usize { + (*self).serialized_length() + } +} + +// This test helper is not intended to be used by third party crates. +#[doc(hidden)] +/// Returns `true` if a we can serialize and then deserialize a value +pub fn test_serialization_roundtrip(t: &T) +where + T: alloc::fmt::Debug + ToBytes + FromBytes + PartialEq, +{ + let serialized = ToBytes::to_bytes(t).expect("Unable to serialize data"); + assert_eq!( + serialized.len(), + t.serialized_length(), + "\nLength of serialized data: {},\nserialized_length() yielded: {},\nserialized data: {:?}, t is {:?}", + serialized.len(), + t.serialized_length(), + serialized, + t + ); + let deserialized = deserialize::(serialized).expect("Unable to deserialize data"); + assert!(*t == deserialized) +} + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + + use super::*; + + #[test] + fn check_array_from_bytes_doesnt_leak() { + thread_local!(static INSTANCE_COUNT: RefCell = RefCell::new(0)); + const MAX_INSTANCES: usize = 10; + + struct LeakChecker; + + impl FromBytes for LeakChecker { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let instance_num = INSTANCE_COUNT.with(|count| *count.borrow()); + if instance_num >= MAX_INSTANCES { + Err(Error::Formatting) + } else { + INSTANCE_COUNT.with(|count| *count.borrow_mut() += 1); + Ok((LeakChecker, bytes)) + } + } + } + + impl Drop for LeakChecker { + fn drop(&mut self) { + INSTANCE_COUNT.with(|count| *count.borrow_mut() -= 1); + } + } + + // Check we can construct an array of `MAX_INSTANCES` of `LeakChecker`s. + { + let bytes = (MAX_INSTANCES as u32).to_bytes().unwrap(); + let _array = <[LeakChecker; MAX_INSTANCES]>::from_bytes(&bytes).unwrap(); + // Assert `INSTANCE_COUNT == MAX_INSTANCES` + INSTANCE_COUNT.with(|count| assert_eq!(MAX_INSTANCES, *count.borrow())); + } + + // Assert the `INSTANCE_COUNT` has dropped to zero again. + INSTANCE_COUNT.with(|count| assert_eq!(0, *count.borrow())); + + // Try to construct an array of `LeakChecker`s where the `MAX_INSTANCES + 1`th instance + // returns an error. + let bytes = (MAX_INSTANCES as u32 + 1).to_bytes().unwrap(); + let result = <[LeakChecker; MAX_INSTANCES + 1]>::from_bytes(&bytes); + assert!(result.is_err()); + + // Assert the `INSTANCE_COUNT` has dropped to zero again. + INSTANCE_COUNT.with(|count| assert_eq!(0, *count.borrow())); + } +} + +#[cfg(test)] +mod proptests { + use std::vec::Vec; + + use proptest::{collection::vec, prelude::*}; + + use crate::{ + bytesrepr::{self, FromBytes, ToBytes, U32_SERIALIZED_LENGTH}, + gens::*, + }; + + proptest! { + #[test] + fn test_bool(u in any::()) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_u8(u in any::()) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_u16(u in any::()) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_u32(u in any::()) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_i32(u in any::()) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_u64(u in any::()) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_i64(u in any::()) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_u8_slice_32(s in u8_slice_32()) { + bytesrepr::test_serialization_roundtrip(&s); + } + + #[test] + fn test_vec_u8(u in vec(any::(), 1..100)) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_vec_i32(u in vec(any::(), 1..100)) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_vec_vec_u8(u in vec(vec(any::(), 1..100), 10)) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_uref_map(m in named_keys_arb(20)) { + bytesrepr::test_serialization_roundtrip(&m); + } + + #[test] + fn test_array_u8_32(arr in any::<[u8; 32]>()) { + bytesrepr::test_serialization_roundtrip(&arr); + } + + #[test] + fn test_string(s in "\\PC*") { + bytesrepr::test_serialization_roundtrip(&s); + } + + #[test] + fn test_option(o in proptest::option::of(key_arb())) { + bytesrepr::test_serialization_roundtrip(&o); + } + + #[test] + fn test_unit(unit in Just(())) { + bytesrepr::test_serialization_roundtrip(&unit); + } + + #[test] + fn test_u128_serialization(u in u128_arb()) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_u256_serialization(u in u256_arb()) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_u512_serialization(u in u512_arb()) { + bytesrepr::test_serialization_roundtrip(&u); + } + + #[test] + fn test_key_serialization(key in key_arb()) { + bytesrepr::test_serialization_roundtrip(&key); + } + + #[test] + fn test_cl_value_serialization(cl_value in cl_value_arb()) { + bytesrepr::test_serialization_roundtrip(&cl_value); + } + + #[test] + fn test_access_rights(access_right in access_rights_arb()) { + bytesrepr::test_serialization_roundtrip(&access_right); + } + + #[test] + fn test_uref(uref in uref_arb()) { + bytesrepr::test_serialization_roundtrip(&uref); + } + + #[test] + fn test_account_hash(pk in account_hash_arb()) { + bytesrepr::test_serialization_roundtrip(&pk); + } + + #[test] + fn test_result(result in result_arb()) { + bytesrepr::test_serialization_roundtrip(&result); + } + + #[test] + fn test_phase_serialization(phase in phase_arb()) { + bytesrepr::test_serialization_roundtrip(&phase); + } + + #[test] + fn test_protocol_version(protocol_version in protocol_version_arb()) { + bytesrepr::test_serialization_roundtrip(&protocol_version); + } + + #[test] + fn test_sem_ver(sem_ver in sem_ver_arb()) { + bytesrepr::test_serialization_roundtrip(&sem_ver); + } + + #[test] + fn test_tuple1(t in (any::(),)) { + bytesrepr::test_serialization_roundtrip(&t); + } + + #[test] + fn test_tuple2(t in (any::(),any::())) { + bytesrepr::test_serialization_roundtrip(&t); + } + + #[test] + fn test_tuple3(t in (any::(),any::(),any::())) { + bytesrepr::test_serialization_roundtrip(&t); + } + } + + #[test] + fn vec_u8_from_bytes() { + let data: Vec = vec![1, 2, 3, 4, 5]; + let data_bytes = data.to_bytes().unwrap(); + assert!(Vec::::from_bytes(&data_bytes[..U32_SERIALIZED_LENGTH / 2]).is_err()); + assert!(Vec::::from_bytes(&data_bytes[..U32_SERIALIZED_LENGTH]).is_err()); + assert!(Vec::::from_bytes(&data_bytes[..U32_SERIALIZED_LENGTH + 2]).is_err()); + } +} diff --git a/types/src/cl_type.rs b/types/src/cl_type.rs new file mode 100644 index 0000000000..088bda7c9a --- /dev/null +++ b/types/src/cl_type.rs @@ -0,0 +1,685 @@ +use alloc::{ + boxed::Box, + collections::{BTreeMap, VecDeque}, + string::String, + vec::Vec, +}; +use core::mem; + +use crate::{ + bytesrepr::{self, FromBytes, ToBytes}, + Key, URef, U128, U256, U512, +}; + +const CL_TYPE_TAG_BOOL: u8 = 0; +const CL_TYPE_TAG_I32: u8 = 1; +const CL_TYPE_TAG_I64: u8 = 2; +const CL_TYPE_TAG_U8: u8 = 3; +const CL_TYPE_TAG_U32: u8 = 4; +const CL_TYPE_TAG_U64: u8 = 5; +const CL_TYPE_TAG_U128: u8 = 6; +const CL_TYPE_TAG_U256: u8 = 7; +const CL_TYPE_TAG_U512: u8 = 8; +const CL_TYPE_TAG_UNIT: u8 = 9; +const CL_TYPE_TAG_STRING: u8 = 10; +const CL_TYPE_TAG_KEY: u8 = 11; +const CL_TYPE_TAG_UREF: u8 = 12; +const CL_TYPE_TAG_OPTION: u8 = 13; +const CL_TYPE_TAG_LIST: u8 = 14; +const CL_TYPE_TAG_FIXED_LIST: u8 = 15; +const CL_TYPE_TAG_RESULT: u8 = 16; +const CL_TYPE_TAG_MAP: u8 = 17; +const CL_TYPE_TAG_TUPLE1: u8 = 18; +const CL_TYPE_TAG_TUPLE2: u8 = 19; +const CL_TYPE_TAG_TUPLE3: u8 = 20; +const CL_TYPE_TAG_ANY: u8 = 21; + +/// CasperLabs types, i.e. types which can be stored and manipulated by smart contracts. +/// +/// Provides a description of the underlying data type of a [`CLValue`](crate::CLValue). +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum CLType { + /// `bool` primitive. + Bool, + /// `i32` primitive. + I32, + /// `i64` primitive. + I64, + /// `u8` primitive. + U8, + /// `u32` primitive. + U32, + /// `u64` primitive. + U64, + /// [`U128`] large unsigned integer type. + U128, + /// [`U256`] large unsigned integer type. + U256, + /// [`U512`] large unsigned integer type. + U512, + /// `()` primitive. + Unit, + /// `String` primitive. + String, + /// [`Key`] system type. + Key, + /// [`URef`] system type. + URef, + /// `Option` of a `CLType`. + Option(Box), + /// Variable-length list of a single `CLType` (comparable to a `Vec`). + List(Box), + /// Fixed-length list of a single `CLType` (comparable to a Rust array). + FixedList(Box, u32), + /// `Result` with `Ok` and `Err` variants of `CLType`s. + #[allow(missing_docs)] // generated docs are explicit enough. + Result { ok: Box, err: Box }, + /// Map with keys of a single `CLType` and values of a single `CLType`. + #[allow(missing_docs)] // generated docs are explicit enough. + Map { + key: Box, + value: Box, + }, + /// 1-ary tuple of a `CLType`. + Tuple1([Box; 1]), + /// 2-ary tuple of `CLType`s. + Tuple2([Box; 2]), + /// 3-ary tuple of `CLType`s. + Tuple3([Box; 3]), + /// Unspecified type. + Any, +} + +impl CLType { + /// The `len()` of the `Vec` resulting from `self.to_bytes()`. + pub fn serialized_length(&self) -> usize { + mem::size_of::() + + match self { + CLType::Bool + | CLType::I32 + | CLType::I64 + | CLType::U8 + | CLType::U32 + | CLType::U64 + | CLType::U128 + | CLType::U256 + | CLType::U512 + | CLType::Unit + | CLType::String + | CLType::Key + | CLType::URef + | CLType::Any => 0, + CLType::Option(cl_type) | CLType::List(cl_type) => cl_type.serialized_length(), + CLType::FixedList(cl_type, list_len) => { + cl_type.serialized_length() + list_len.to_le_bytes().len() + } + CLType::Result { ok, err } => ok.serialized_length() + err.serialized_length(), + CLType::Map { key, value } => key.serialized_length() + value.serialized_length(), + CLType::Tuple1(cl_type_array) => serialized_length_of_cl_tuple_type(cl_type_array), + CLType::Tuple2(cl_type_array) => serialized_length_of_cl_tuple_type(cl_type_array), + CLType::Tuple3(cl_type_array) => serialized_length_of_cl_tuple_type(cl_type_array), + } + } +} + +/// Returns the `CLType` describing a "named key" on the system, i.e. a `(String, Key)`. +pub fn named_key_type() -> CLType { + CLType::Tuple2([Box::new(CLType::String), Box::new(CLType::Key)]) +} + +impl CLType { + pub(crate) fn append_bytes(&self, stream: &mut Vec) { + match self { + CLType::Bool => stream.push(CL_TYPE_TAG_BOOL), + CLType::I32 => stream.push(CL_TYPE_TAG_I32), + CLType::I64 => stream.push(CL_TYPE_TAG_I64), + CLType::U8 => stream.push(CL_TYPE_TAG_U8), + CLType::U32 => stream.push(CL_TYPE_TAG_U32), + CLType::U64 => stream.push(CL_TYPE_TAG_U64), + CLType::U128 => stream.push(CL_TYPE_TAG_U128), + CLType::U256 => stream.push(CL_TYPE_TAG_U256), + CLType::U512 => stream.push(CL_TYPE_TAG_U512), + CLType::Unit => stream.push(CL_TYPE_TAG_UNIT), + CLType::String => stream.push(CL_TYPE_TAG_STRING), + CLType::Key => stream.push(CL_TYPE_TAG_KEY), + CLType::URef => stream.push(CL_TYPE_TAG_UREF), + CLType::Option(cl_type) => { + stream.push(CL_TYPE_TAG_OPTION); + cl_type.append_bytes(stream); + } + CLType::List(cl_type) => { + stream.push(CL_TYPE_TAG_LIST); + cl_type.append_bytes(stream); + } + CLType::FixedList(cl_type, len) => { + stream.push(CL_TYPE_TAG_FIXED_LIST); + cl_type.append_bytes(stream); + stream.append(&mut len.to_bytes().unwrap()); + } + CLType::Result { ok, err } => { + stream.push(CL_TYPE_TAG_RESULT); + ok.append_bytes(stream); + err.append_bytes(stream); + } + CLType::Map { key, value } => { + stream.push(CL_TYPE_TAG_MAP); + key.append_bytes(stream); + value.append_bytes(stream); + } + CLType::Tuple1(cl_type_array) => { + serialize_cl_tuple_type(CL_TYPE_TAG_TUPLE1, cl_type_array, stream) + } + CLType::Tuple2(cl_type_array) => { + serialize_cl_tuple_type(CL_TYPE_TAG_TUPLE2, cl_type_array, stream) + } + CLType::Tuple3(cl_type_array) => { + serialize_cl_tuple_type(CL_TYPE_TAG_TUPLE3, cl_type_array, stream) + } + CLType::Any => stream.push(CL_TYPE_TAG_ANY), + } + } +} + +#[allow(clippy::cognitive_complexity)] +impl FromBytes for CLType { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, remainder) = u8::from_bytes(bytes)?; + match tag { + CL_TYPE_TAG_BOOL => Ok((CLType::Bool, remainder)), + CL_TYPE_TAG_I32 => Ok((CLType::I32, remainder)), + CL_TYPE_TAG_I64 => Ok((CLType::I64, remainder)), + CL_TYPE_TAG_U8 => Ok((CLType::U8, remainder)), + CL_TYPE_TAG_U32 => Ok((CLType::U32, remainder)), + CL_TYPE_TAG_U64 => Ok((CLType::U64, remainder)), + CL_TYPE_TAG_U128 => Ok((CLType::U128, remainder)), + CL_TYPE_TAG_U256 => Ok((CLType::U256, remainder)), + CL_TYPE_TAG_U512 => Ok((CLType::U512, remainder)), + CL_TYPE_TAG_UNIT => Ok((CLType::Unit, remainder)), + CL_TYPE_TAG_STRING => Ok((CLType::String, remainder)), + CL_TYPE_TAG_KEY => Ok((CLType::Key, remainder)), + CL_TYPE_TAG_UREF => Ok((CLType::URef, remainder)), + CL_TYPE_TAG_OPTION => { + let (inner_type, remainder) = CLType::from_bytes(remainder)?; + let cl_type = CLType::Option(Box::new(inner_type)); + Ok((cl_type, remainder)) + } + CL_TYPE_TAG_LIST => { + let (inner_type, remainder) = CLType::from_bytes(remainder)?; + let cl_type = CLType::List(Box::new(inner_type)); + Ok((cl_type, remainder)) + } + CL_TYPE_TAG_FIXED_LIST => { + let (inner_type, remainder) = CLType::from_bytes(remainder)?; + let (len, remainder) = u32::from_bytes(remainder)?; + let cl_type = CLType::FixedList(Box::new(inner_type), len); + Ok((cl_type, remainder)) + } + CL_TYPE_TAG_RESULT => { + let (ok_type, remainder) = CLType::from_bytes(remainder)?; + let (err_type, remainder) = CLType::from_bytes(remainder)?; + let cl_type = CLType::Result { + ok: Box::new(ok_type), + err: Box::new(err_type), + }; + Ok((cl_type, remainder)) + } + CL_TYPE_TAG_MAP => { + let (key_type, remainder) = CLType::from_bytes(remainder)?; + let (value_type, remainder) = CLType::from_bytes(remainder)?; + let cl_type = CLType::Map { + key: Box::new(key_type), + value: Box::new(value_type), + }; + Ok((cl_type, remainder)) + } + CL_TYPE_TAG_TUPLE1 => { + let (mut inner_types, remainder) = parse_cl_tuple_types(1, remainder)?; + let cl_type = CLType::Tuple1([inner_types.pop_front().unwrap()]); + Ok((cl_type, remainder)) + } + CL_TYPE_TAG_TUPLE2 => { + let (mut inner_types, remainder) = parse_cl_tuple_types(2, remainder)?; + let cl_type = CLType::Tuple2([ + inner_types.pop_front().unwrap(), + inner_types.pop_front().unwrap(), + ]); + Ok((cl_type, remainder)) + } + CL_TYPE_TAG_TUPLE3 => { + let (mut inner_types, remainder) = parse_cl_tuple_types(3, remainder)?; + let cl_type = CLType::Tuple3([ + inner_types.pop_front().unwrap(), + inner_types.pop_front().unwrap(), + inner_types.pop_front().unwrap(), + ]); + Ok((cl_type, remainder)) + } + CL_TYPE_TAG_ANY => Ok((CLType::Any, remainder)), + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +fn serialize_cl_tuple_type<'a, T: IntoIterator>>( + tag: u8, + cl_type_array: T, + stream: &mut Vec, +) { + stream.push(tag); + for cl_type in cl_type_array { + cl_type.append_bytes(stream); + } +} + +fn parse_cl_tuple_types( + count: usize, + mut bytes: &[u8], +) -> Result<(VecDeque>, &[u8]), bytesrepr::Error> { + let mut cl_types = VecDeque::with_capacity(count); + for _ in 0..count { + let (cl_type, remainder) = CLType::from_bytes(bytes)?; + cl_types.push_back(Box::new(cl_type)); + bytes = remainder; + } + + Ok((cl_types, bytes)) +} + +fn serialized_length_of_cl_tuple_type<'a, T: IntoIterator>>( + cl_type_array: T, +) -> usize { + cl_type_array + .into_iter() + .map(|cl_type| cl_type.serialized_length()) + .sum() +} + +/// A type which can be described as a [`CLType`]. +pub trait CLTyped { + /// The `CLType` of `Self`. + fn cl_type() -> CLType; +} + +impl CLTyped for bool { + fn cl_type() -> CLType { + CLType::Bool + } +} + +impl CLTyped for i32 { + fn cl_type() -> CLType { + CLType::I32 + } +} + +impl CLTyped for i64 { + fn cl_type() -> CLType { + CLType::I64 + } +} + +impl CLTyped for u8 { + fn cl_type() -> CLType { + CLType::U8 + } +} + +impl CLTyped for u32 { + fn cl_type() -> CLType { + CLType::U32 + } +} + +impl CLTyped for u64 { + fn cl_type() -> CLType { + CLType::U64 + } +} + +impl CLTyped for U128 { + fn cl_type() -> CLType { + CLType::U128 + } +} + +impl CLTyped for U256 { + fn cl_type() -> CLType { + CLType::U256 + } +} + +impl CLTyped for U512 { + fn cl_type() -> CLType { + CLType::U512 + } +} + +impl CLTyped for () { + fn cl_type() -> CLType { + CLType::Unit + } +} + +impl CLTyped for String { + fn cl_type() -> CLType { + CLType::String + } +} + +impl CLTyped for &str { + fn cl_type() -> CLType { + CLType::String + } +} + +impl CLTyped for Key { + fn cl_type() -> CLType { + CLType::Key + } +} + +impl CLTyped for URef { + fn cl_type() -> CLType { + CLType::URef + } +} + +impl CLTyped for Option { + fn cl_type() -> CLType { + CLType::Option(Box::new(T::cl_type())) + } +} + +impl CLTyped for Vec { + fn cl_type() -> CLType { + CLType::List(Box::new(T::cl_type())) + } +} + +macro_rules! impl_cl_typed_for_array { + ($($N:literal)+) => { + $( + impl CLTyped for [T; $N] { + fn cl_type() -> CLType { + CLType::FixedList(Box::new(T::cl_type()), $N as u32) + } + } + )+ + } +} + +impl_cl_typed_for_array! { + 0 1 2 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 28 29 + 30 31 32 + 64 128 256 512 +} + +impl CLTyped for Result { + fn cl_type() -> CLType { + let ok = Box::new(T::cl_type()); + let err = Box::new(E::cl_type()); + CLType::Result { ok, err } + } +} + +impl CLTyped for BTreeMap { + fn cl_type() -> CLType { + let key = Box::new(K::cl_type()); + let value = Box::new(V::cl_type()); + CLType::Map { key, value } + } +} + +impl CLTyped for (T1,) { + fn cl_type() -> CLType { + CLType::Tuple1([Box::new(T1::cl_type())]) + } +} + +impl CLTyped for (T1, T2) { + fn cl_type() -> CLType { + CLType::Tuple2([Box::new(T1::cl_type()), Box::new(T2::cl_type())]) + } +} + +impl CLTyped for (T1, T2, T3) { + fn cl_type() -> CLType { + CLType::Tuple3([ + Box::new(T1::cl_type()), + Box::new(T2::cl_type()), + Box::new(T3::cl_type()), + ]) + } +} + +#[cfg(test)] +mod tests { + use std::{fmt::Debug, string::ToString}; + + use super::*; + use crate::{ + bytesrepr::{FromBytes, ToBytes}, + AccessRights, CLValue, + }; + + fn round_trip(value: &T) { + let cl_value = CLValue::from_t(value.clone()).unwrap(); + + let serialized_cl_value = cl_value.to_bytes().unwrap(); + assert_eq!(serialized_cl_value.len(), cl_value.serialized_length()); + let parsed_cl_value: CLValue = bytesrepr::deserialize(serialized_cl_value).unwrap(); + assert_eq!(cl_value, parsed_cl_value); + + let parsed_value = CLValue::into_t(cl_value).unwrap(); + assert_eq!(*value, parsed_value); + } + + #[test] + fn bool_should_work() { + round_trip(&true); + round_trip(&false); + } + + #[test] + fn u8_should_work() { + round_trip(&1u8); + } + + #[test] + fn u32_should_work() { + round_trip(&1u32); + } + + #[test] + fn i32_should_work() { + round_trip(&-1i32); + } + + #[test] + fn u64_should_work() { + round_trip(&1u64); + } + + #[test] + fn i64_should_work() { + round_trip(&-1i64); + } + + #[test] + fn u128_should_work() { + round_trip(&U128::one()); + } + + #[test] + fn u256_should_work() { + round_trip(&U256::one()); + } + + #[test] + fn u512_should_work() { + round_trip(&U512::one()); + } + + #[test] + fn unit_should_work() { + round_trip(&()); + } + + #[test] + fn string_should_work() { + round_trip(&String::from("abc")); + } + + #[test] + fn key_should_work() { + let key = Key::URef(URef::new([0u8; 32], AccessRights::READ_ADD_WRITE)); + round_trip(&key); + } + + #[test] + fn uref_should_work() { + let uref = URef::new([0u8; 32], AccessRights::READ_ADD_WRITE); + round_trip(&uref); + } + + #[test] + fn option_of_cl_type_should_work() { + let x: Option = Some(-1); + let y: Option = None; + + round_trip(&x); + round_trip(&y); + } + + #[test] + fn vec_of_cl_type_should_work() { + let vec = vec![String::from("a"), String::from("b")]; + round_trip(&vec); + } + + #[test] + #[allow(clippy::cognitive_complexity)] + fn small_array_of_cl_type_should_work() { + macro_rules! test_small_array { + ($($N:literal)+) => { + $( + let mut array = [0u64; $N]; + for i in 0..$N { + array[i] = i as u64; + } + round_trip(&array); + )+ + } + } + + test_small_array! { + 1 2 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 28 29 + 30 31 32 + } + } + + #[test] + fn large_array_of_cl_type_should_work() { + macro_rules! test_large_array { + ($($N:literal)+) => { + $( + let array = { + let mut tmp = [0u64; $N]; + for i in 0..$N { + tmp[i] = i as u64; + } + tmp + }; + + let cl_value = CLValue::from_t(array.clone()).unwrap(); + + let serialized_cl_value = cl_value.to_bytes().unwrap(); + let parsed_cl_value: CLValue = bytesrepr::deserialize(serialized_cl_value).unwrap(); + assert_eq!(cl_value, parsed_cl_value); + + let parsed_value: [u64; $N] = CLValue::into_t(cl_value).unwrap(); + for i in 0..$N { + assert_eq!(array[i], parsed_value[i]); + } + )+ + } + } + + test_large_array! { 64 128 256 512 } + } + + #[test] + fn result_of_cl_type_should_work() { + let x: Result<(), String> = Ok(()); + let y: Result<(), String> = Err(String::from("Hello, world!")); + + round_trip(&x); + round_trip(&y); + } + + #[test] + fn map_of_cl_type_should_work() { + let mut map: BTreeMap = BTreeMap::new(); + map.insert(String::from("abc"), 1); + map.insert(String::from("xyz"), 2); + + round_trip(&map); + } + + #[test] + fn tuple_1_should_work() { + let x = (-1i32,); + + round_trip(&x); + } + + #[test] + fn tuple_2_should_work() { + let x = (-1i32, String::from("a")); + + round_trip(&x); + } + + #[test] + fn tuple_3_should_work() { + let x = (-1i32, 1u32, String::from("a")); + + round_trip(&x); + } + + #[test] + fn any_should_work() { + #[derive(PartialEq, Debug, Clone)] + struct Any(String); + + impl CLTyped for Any { + fn cl_type() -> CLType { + CLType::Any + } + } + + impl ToBytes for Any { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } + } + + impl FromBytes for Any { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (inner, remainder) = String::from_bytes(bytes)?; + Ok((Any(inner), remainder)) + } + } + + let any = Any("Any test".to_string()); + round_trip(&any); + } +} diff --git a/types/src/cl_value.rs b/types/src/cl_value.rs new file mode 100644 index 0000000000..60bbe9232a --- /dev/null +++ b/types/src/cl_value.rs @@ -0,0 +1,132 @@ +use alloc::vec::Vec; +use core::fmt; + +use failure::Fail; + +use crate::{ + bytesrepr::{self, FromBytes, ToBytes, U32_SERIALIZED_LENGTH}, + CLType, CLTyped, +}; + +/// Error while converting a [`CLValue`] into a given type. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct CLTypeMismatch { + /// The [`CLType`] into which the `CLValue` was being converted. + pub expected: CLType, + /// The actual underlying [`CLType`] of this `CLValue`, i.e. the type from which it was + /// constructed. + pub found: CLType, +} + +impl fmt::Display for CLTypeMismatch { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + f, + "Expected {:?} but found {:?}.", + self.expected, self.found + ) + } +} + +/// Error relating to [`CLValue`] operations. +#[derive(Fail, PartialEq, Eq, Clone, Debug)] +pub enum CLValueError { + /// An error while serializing or deserializing the underlying data. + #[fail(display = "CLValue error: {}", _0)] + Serialization(bytesrepr::Error), + /// A type mismatch while trying to convert a [`CLValue`] into a given type. + #[fail(display = "Type mismatch: {}", _0)] + Type(CLTypeMismatch), +} + +/// A CasperLabs value, i.e. a value which can be stored and manipulated by smart contracts. +/// +/// It holds the underlying data as a type-erased, serialized `Vec` and also holds the +/// [`CLType`] of the underlying data as a separate member. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct CLValue { + cl_type: CLType, + bytes: Vec, +} + +impl CLValue { + /// Constructs a `CLValue` from `t`. + pub fn from_t(t: T) -> Result { + let bytes = t.into_bytes().map_err(CLValueError::Serialization)?; + + Ok(CLValue { + cl_type: T::cl_type(), + bytes, + }) + } + + /// Consumes and converts `self` back into its underlying type. + pub fn into_t(self) -> Result { + let expected = T::cl_type(); + + if self.cl_type == expected { + bytesrepr::deserialize(self.bytes).map_err(CLValueError::Serialization) + } else { + Err(CLValueError::Type(CLTypeMismatch { + expected, + found: self.cl_type, + })) + } + } + + // This is only required in order to implement `TryFrom for CLValue` (i.e. the + // conversion from the Protobuf `CLValue`) in a separate module to this one. + #[doc(hidden)] + pub fn from_components(cl_type: CLType, bytes: Vec) -> Self { + Self { cl_type, bytes } + } + + // This is only required in order to implement `From for state::CLValue` (i.e. the + // conversion to the Protobuf `CLValue`) in a separate module to this one. + #[doc(hidden)] + pub fn destructure(self) -> (CLType, Vec) { + (self.cl_type, self.bytes) + } + + /// The [`CLType`] of the underlying data. + pub fn cl_type(&self) -> &CLType { + &self.cl_type + } + + /// Returns a reference to the serialized form of the underlying value held in this `CLValue`. + pub fn inner_bytes(&self) -> &Vec { + &self.bytes + } + + /// Returns the length of the `Vec` yielded after calling `self.to_bytes()`. + /// + /// Note, this method doesn't actually serialize `self`, and hence is relatively cheap. + pub fn serialized_length(&self) -> usize { + self.cl_type.serialized_length() + U32_SERIALIZED_LENGTH + self.bytes.len() + } +} + +impl ToBytes for CLValue { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + self.clone().into_bytes() + } + + fn into_bytes(self) -> Result, bytesrepr::Error> { + let mut result = self.bytes.into_bytes()?; + self.cl_type.append_bytes(&mut result); + Ok(result) + } + + fn serialized_length(&self) -> usize { + self.bytes.serialized_length() + self.cl_type.serialized_length() + } +} + +impl FromBytes for CLValue { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (bytes, remainder) = Vec::::from_bytes(bytes)?; + let (cl_type, remainder) = CLType::from_bytes(remainder)?; + let cl_value = CLValue { cl_type, bytes }; + Ok((cl_value, remainder)) + } +} diff --git a/types/src/contract_wasm.rs b/types/src/contract_wasm.rs new file mode 100644 index 0000000000..bc0366be94 --- /dev/null +++ b/types/src/contract_wasm.rs @@ -0,0 +1,87 @@ +use crate::bytesrepr::{Error, FromBytes, ToBytes}; +use alloc::vec::Vec; +use core::fmt::Debug; + +const CONTRACT_WASM_MAX_DISPLAY_LEN: usize = 16; + +/// A container for contract's WASM bytes. +#[derive(PartialEq, Eq, Clone)] +pub struct ContractWasm { + bytes: Vec, +} + +impl Debug for ContractWasm { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + if self.bytes.len() > CONTRACT_WASM_MAX_DISPLAY_LEN { + write!( + f, + "ContractWasm(0x{}...)", + base16::encode_lower(&self.bytes[..CONTRACT_WASM_MAX_DISPLAY_LEN]) + ) + } else { + write!(f, "ContractWasm(0x{})", base16::encode_lower(&self.bytes)) + } + } +} + +impl ContractWasm { + /// Creates new WASM object from bytes. + pub fn new(bytes: Vec) -> Self { + ContractWasm { bytes } + } + + /// Consumes instance of [`ContractWasm`] and returns its bytes. + pub fn take_bytes(self) -> Vec { + self.bytes + } + + /// Returns a slice of contained WASM bytes. + pub fn bytes(&self) -> &[u8] { + &self.bytes + } +} + +impl ToBytes for ContractWasm { + fn to_bytes(&self) -> Result, Error> { + self.bytes.to_bytes() + } + + fn serialized_length(&self) -> usize { + self.bytes.serialized_length() + } +} + +impl FromBytes for ContractWasm { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (bytes, rem1) = Vec::::from_bytes(bytes)?; + Ok((ContractWasm { bytes }, rem1)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_debug_repr_of_short_wasm() { + const SIZE: usize = 8; + let wasm_bytes = vec![0; SIZE]; + let contract_wasm = ContractWasm::new(wasm_bytes); + // String output is less than the bytes itself + assert_eq!( + format!("{:?}", contract_wasm), + "ContractWasm(0x0000000000000000)" + ); + } + + #[test] + fn test_debug_repr_of_long_wasm() { + const SIZE: usize = 65; + let wasm_bytes = vec![0; SIZE]; + let contract_wasm = ContractWasm::new(wasm_bytes); + // String output is less than the bytes itself + assert_eq!( + format!("{:?}", contract_wasm), + "ContractWasm(0x00000000000000000000000000000000...)" + ); + } +} diff --git a/types/src/contracts.rs b/types/src/contracts.rs new file mode 100644 index 0000000000..fcfdc5027b --- /dev/null +++ b/types/src/contracts.rs @@ -0,0 +1,1052 @@ +//! Data types for supporting contract headers feature. + +use crate::{ + alloc::string::ToString, + bytesrepr::{self, FromBytes, ToBytes, U32_SERIALIZED_LENGTH}, + uref::URef, + CLType, ContractHash, ContractPackageHash, ContractWasmHash, Key, ProtocolVersion, + KEY_HASH_LENGTH, +}; +use alloc::{ + collections::{BTreeMap, BTreeSet}, + string::String, + vec::Vec, +}; +use core::fmt; + +/// Maximum number of distinct user groups. +pub const MAX_GROUPS: u8 = 10; +/// Maximum number of URefs which can be assigned across all user groups. +pub const MAX_TOTAL_UREFS: usize = 100; + +/// Set of errors which may happen when working with contract headers. +#[derive(Debug, PartialEq)] +#[repr(u8)] +pub enum Error { + /// Attempt to override an existing or previously existing version with a + /// new header (this is not allowed to ensure immutability of a given + /// version). + PreviouslyUsedVersion = 1, + /// Attempted to disable a contract that does not exist. + ContractNotFound = 2, + /// Attempted to create a user group which already exists (use the update + /// function to change an existing user group). + GroupAlreadyExists = 3, + /// Attempted to add a new user group which exceeds the allowed maximum + /// number of groups. + MaxGroupsExceeded = 4, + /// Attempted to add a new URef to a group, which resulted in the total + /// number of URefs across all user groups to exceed the allowed maximum. + MaxTotalURefsExceeded = 5, + /// Attempted to remove a URef from a group, which does not exist in the + /// group. + GroupDoesNotExist = 6, + /// Attempted to remove unknown URef from the group. + UnableToRemoveURef = 7, + /// Group is use by at least one active contract. + GroupInUse = 8, + /// URef already exists in given group. + URefAlreadyExists = 9, +} + +/// A (labelled) "user group". Each method of a versioned contract may be +/// assoicated with one or more user groups which are allowed to call it. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Group(String); + +impl Group { + /// Basic constructor + pub fn new>(s: T) -> Self { + Group(s.into()) + } + + /// Retrieves underlying name. + pub fn value(&self) -> &str { + &self.0 + } +} + +impl From for String { + fn from(group: Group) -> Self { + group.0 + } +} + +impl ToBytes for Group { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } +} + +impl FromBytes for Group { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + String::from_bytes(bytes).map(|(label, bytes)| (Group(label), bytes)) + } +} + +/// Automatically incremented value for a contract version within a major `ProtocolVersion`. +pub type ContractVersion = u32; + +/// Within each discrete major `ProtocolVersion`, contract version resets to this value. +pub const CONTRACT_INITIAL_VERSION: ContractVersion = 1; + +/// Major element of `ProtocolVersion` a `ContractVersion` is compatible with. +pub type ProtocolVersionMajor = u32; + +/// Major element of `ProtocolVersion` combined with `ContractVersion`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct ContractVersionKey(ProtocolVersionMajor, ContractVersion); + +impl ContractVersionKey { + /// Returns a new instance of ContractVersionKey with provided values. + pub fn new( + protocol_version_major: ProtocolVersionMajor, + contract_version: ContractVersion, + ) -> Self { + Self(protocol_version_major, contract_version) + } + + /// Returns the major element of the protocol version this contract is compatible with. + pub fn protocol_version_major(self) -> ProtocolVersionMajor { + self.0 + } + + /// Returns the contract version within the protocol major version. + pub fn contract_version(self) -> ContractVersion { + self.1 + } +} + +impl From for (ProtocolVersionMajor, ContractVersion) { + fn from(contract_version_key: ContractVersionKey) -> Self { + (contract_version_key.0, contract_version_key.1) + } +} + +/// Serialized length of `ContractVersionKey`. +pub const CONTRACT_VERSION_KEY_SERIALIZED_LENGTH: usize = + U32_SERIALIZED_LENGTH + U32_SERIALIZED_LENGTH; + +impl ToBytes for ContractVersionKey { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut ret = bytesrepr::unchecked_allocate_buffer(self); + ret.append(&mut self.0.to_bytes()?); + ret.append(&mut self.1.to_bytes()?); + Ok(ret) + } + + fn serialized_length(&self) -> usize { + CONTRACT_VERSION_KEY_SERIALIZED_LENGTH + } +} + +impl FromBytes for ContractVersionKey { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (major, rem): (u32, &[u8]) = FromBytes::from_bytes(bytes)?; + let (contract, rem): (ContractVersion, &[u8]) = FromBytes::from_bytes(rem)?; + Ok((ContractVersionKey::new(major, contract), rem)) + } +} + +impl fmt::Display for ContractVersionKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}.{}", self.0, self.1) + } +} + +/// Collection of contract versions. +pub type ContractVersions = BTreeMap; + +/// Collection of disabled contract versions. The runtime will not permit disabled +/// contract versions to be executed. +pub type DisabledVersions = BTreeSet; + +/// Collection of named groups. +pub type Groups = BTreeMap>; + +/// Contract definition, metadata, and security container. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct ContractPackage { + /// Key used to add or disable versions + access_key: URef, + /// All versions (enabled & disabled) + versions: ContractVersions, + /// Disabled versions + disabled_versions: DisabledVersions, + /// Mapping maintaining the set of URefs associated with each "user + /// group". This can be used to control access to methods in a particular + /// version of the contract. A method is callable by any context which + /// "knows" any of the URefs assoicated with the mthod's user group. + groups: Groups, +} + +impl ContractPackage { + /// Create new `ContractPackage` (with no versions) from given access key. + pub fn new( + access_key: URef, + versions: ContractVersions, + disabled_versions: DisabledVersions, + groups: Groups, + ) -> Self { + ContractPackage { + access_key, + versions, + disabled_versions, + groups, + } + } + + /// Get the access key for this contract. + pub fn access_key(&self) -> URef { + self.access_key + } + + /// Get the mutable group definitions for this contract. + pub fn groups_mut(&mut self) -> &mut Groups { + &mut self.groups + } + + /// Get the group definitions for this contract. + pub fn groups(&self) -> &Groups { + &self.groups + } + + /// Adds new group to this contract. + pub fn add_group(&mut self, group: Group, urefs: BTreeSet) { + let v = self.groups.entry(group).or_insert_with(Default::default); + v.extend(urefs) + } + + /// Lookup the contract hash for a given contract version (if present) + pub fn lookup_contract_hash( + &self, + contract_version_key: ContractVersionKey, + ) -> Option<&ContractHash> { + if !self.is_version_enabled(contract_version_key) { + return None; + } + self.versions.get(&contract_version_key) + } + + /// Checks if the given contract version exists and is available for use. + pub fn is_version_enabled(&self, contract_version_key: ContractVersionKey) -> bool { + !self.disabled_versions.contains(&contract_version_key) + && self.versions.contains_key(&contract_version_key) + } + + /// Insert a new contract version; the next sequential version number will be issued. + pub fn insert_contract_version( + &mut self, + protocol_version_major: ProtocolVersionMajor, + contract_hash: ContractHash, + ) -> ContractVersionKey { + let contract_version = self.next_contract_version_for(protocol_version_major); + let key = ContractVersionKey::new(protocol_version_major, contract_version); + self.versions.insert(key, contract_hash); + key + } + + /// Disable the contract version corresponding to the given hash (if it exists). + pub fn disable_contract_version(&mut self, contract_hash: ContractHash) -> Result<(), Error> { + let contract_version_key = self + .versions + .iter() + .filter_map(|(k, v)| if *v == contract_hash { Some(*k) } else { None }) + .next() + .ok_or(Error::ContractNotFound)?; + + if !self.disabled_versions.contains(&contract_version_key) { + self.disabled_versions.insert(contract_version_key); + } + + Ok(()) + } + + /// Returns reference to all of this contract's versions. + pub fn versions(&self) -> &ContractVersions { + &self.versions + } + + /// Returns all of this contract's enabled contract versions. + pub fn enabled_versions(&self) -> ContractVersions { + let mut ret = ContractVersions::new(); + for version in &self.versions { + if !self.is_version_enabled(*version.0) { + continue; + } + ret.insert(*version.0, *version.1); + } + ret + } + + /// Returns mutable reference to all of this contract's versions (enabled and disabled). + pub fn versions_mut(&mut self) -> &mut ContractVersions { + &mut self.versions + } + + /// Consumes the object and returns all of this contract's versions (enabled and disabled). + pub fn take_versions(self) -> ContractVersions { + self.versions + } + + /// Returns all of this contract's disabled versions. + pub fn disabled_versions(&self) -> &DisabledVersions { + &self.disabled_versions + } + + /// Returns mut reference to all of this contract's disabled versions. + pub fn disabled_versions_mut(&mut self) -> &mut DisabledVersions { + &mut self.disabled_versions + } + + /// Removes a group from this contract (if it exists). + pub fn remove_group(&mut self, group: &Group) -> bool { + self.groups.remove(group).is_some() + } + + /// Gets the next available contract version for the given protocol version + fn next_contract_version_for(&self, protocol_version: ProtocolVersionMajor) -> ContractVersion { + let current_version = self + .versions + .keys() + .rev() + .find_map(|&contract_version_key| { + if contract_version_key.protocol_version_major() == protocol_version { + Some(contract_version_key.contract_version()) + } else { + None + } + }) + .unwrap_or(0); + + current_version + 1 + } + + /// Return the contract version key for the newest enabled contract version. + pub fn current_contract_version(&self) -> Option { + match self.enabled_versions().keys().next_back() { + Some(contract_version_key) => Some(*contract_version_key), + None => None, + } + } + + /// Return the contract hash for the newest enabled contract version. + pub fn current_contract_hash(&self) -> Option { + match self.enabled_versions().values().next_back() { + Some(contract_hash) => Some(*contract_hash), + None => None, + } + } +} + +impl ToBytes for ContractPackage { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut result = bytesrepr::allocate_buffer(self)?; + + result.append(&mut self.access_key.to_bytes()?); + result.append(&mut self.versions.to_bytes()?); + result.append(&mut self.disabled_versions.to_bytes()?); + result.append(&mut self.groups.to_bytes()?); + + Ok(result) + } + + fn serialized_length(&self) -> usize { + self.access_key.serialized_length() + + self.versions.serialized_length() + + self.disabled_versions.serialized_length() + + self.groups.serialized_length() + } +} + +impl FromBytes for ContractPackage { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (access_key, bytes) = URef::from_bytes(bytes)?; + let (versions, bytes) = ContractVersions::from_bytes(bytes)?; + let (disabled_versions, bytes) = DisabledVersions::from_bytes(bytes)?; + let (groups, bytes) = Groups::from_bytes(bytes)?; + let result = ContractPackage { + access_key, + versions, + disabled_versions, + groups, + }; + + Ok((result, bytes)) + } +} + +/// Type alias for a container used inside [`EntryPoints`]. +pub type EntryPointsMap = BTreeMap; + +/// Collection of named entry points +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EntryPoints(EntryPointsMap); + +impl Default for EntryPoints { + fn default() -> Self { + let mut entry_points = EntryPoints::new(); + let entry_point = EntryPoint::default(); + entry_points.add_entry_point(entry_point); + entry_points + } +} + +impl ToBytes for EntryPoints { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + self.0.to_bytes() + } + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } +} + +impl FromBytes for EntryPoints { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (entry_points_map, rem) = EntryPointsMap::from_bytes(bytes)?; + Ok((EntryPoints(entry_points_map), rem)) + } +} + +impl EntryPoints { + /// Creates empty instance of [`EntryPoints`]. + pub fn new() -> EntryPoints { + EntryPoints(EntryPointsMap::new()) + } + + /// Adds new [`EntryPoint`]. + pub fn add_entry_point(&mut self, entry_point: EntryPoint) { + self.0.insert(entry_point.name().to_string(), entry_point); + } + + /// Checks if given [`EntryPoint`] exists. + pub fn has_entry_point(&self, entry_point_name: &str) -> bool { + self.0.contains_key(entry_point_name) + } + + /// Gets an existing [`EntryPoint`] by its name. + pub fn get(&self, entry_point_name: &str) -> Option<&EntryPoint> { + self.0.get(entry_point_name) + } + + /// Returns iterator for existing entry point names. + pub fn keys(&self) -> impl Iterator { + self.0.keys() + } + + /// Takes all entry points. + pub fn take_entry_points(self) -> Vec { + self.0.into_iter().map(|(_name, value)| value).collect() + } +} + +impl From> for EntryPoints { + fn from(entry_points: Vec) -> EntryPoints { + let entries = entry_points + .into_iter() + .map(|entry_point| (String::from(entry_point.name()), entry_point)) + .collect(); + EntryPoints(entries) + } +} + +/// Collection of named keys +pub type NamedKeys = BTreeMap; + +/// Methods and type signatures supported by a contract. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Contract { + contract_package_hash: ContractPackageHash, + contract_wasm_hash: ContractWasmHash, + named_keys: NamedKeys, + entry_points: EntryPoints, + protocol_version: ProtocolVersion, +} + +impl From + for ( + ContractPackageHash, + ContractWasmHash, + NamedKeys, + EntryPoints, + ProtocolVersion, + ) +{ + fn from(contract: Contract) -> Self { + ( + contract.contract_package_hash, + contract.contract_wasm_hash, + contract.named_keys, + contract.entry_points, + contract.protocol_version, + ) + } +} + +impl Contract { + /// `Contract` constructor. + pub fn new( + contract_package_hash: ContractPackageHash, + contract_wasm_hash: ContractWasmHash, + named_keys: NamedKeys, + entry_points: EntryPoints, + protocol_version: ProtocolVersion, + ) -> Self { + Contract { + contract_package_hash, + contract_wasm_hash, + named_keys, + entry_points, + protocol_version, + } + } + + /// Hash for accessing contract package + pub fn contract_package_hash(&self) -> ContractPackageHash { + self.contract_package_hash + } + + /// Hash for accessing contract WASM + pub fn contract_wasm_hash(&self) -> ContractWasmHash { + self.contract_wasm_hash + } + + /// Checks whether there is a method with the given name + pub fn has_entry_point(&self, name: &str) -> bool { + self.entry_points.has_entry_point(name) + } + + /// Returns the type signature for the given `method`. + pub fn entry_point(&self, method: &str) -> Option<&EntryPoint> { + self.entry_points.get(method) + } + + /// Get the protocol version this header is targeting. + pub fn protocol_version(&self) -> ProtocolVersion { + self.protocol_version + } + + /// Adds new entry point + pub fn add_entry_point>(&mut self, entry_point: EntryPoint) { + self.entry_points.add_entry_point(entry_point); + } + + /// Hash for accessing contract bytes + pub fn contract_wasm_key(&self) -> Key { + self.contract_wasm_hash.into() + } + + /// Returns immutable reference to methods + pub fn entry_points(&self) -> &EntryPoints { + &self.entry_points + } + + /// Takes `named_keys` + pub fn take_named_keys(self) -> NamedKeys { + self.named_keys + } + + /// Returns a reference to `named_keys` + pub fn named_keys(&self) -> &NamedKeys { + &self.named_keys + } + + /// Appends `keys` to `named_keys` + pub fn named_keys_append(&mut self, keys: &mut NamedKeys) { + self.named_keys.append(keys); + } + + /// Removes given named key. + pub fn remove_named_key(&mut self, key: &str) -> Option { + self.named_keys.remove(key) + } + + /// Determines if `Contract` is compatibile with a given `ProtocolVersion`. + pub fn is_compatible_protocol_version(&self, protocol_version: ProtocolVersion) -> bool { + self.protocol_version.value().major == protocol_version.value().major + } +} + +impl ToBytes for Contract { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut result = bytesrepr::allocate_buffer(self)?; + result.append(&mut self.contract_package_hash.to_bytes()?); + result.append(&mut self.contract_wasm_hash.to_bytes()?); + result.append(&mut self.named_keys.to_bytes()?); + result.append(&mut self.entry_points.to_bytes()?); + result.append(&mut self.protocol_version.to_bytes()?); + Ok(result) + } + + fn serialized_length(&self) -> usize { + ToBytes::serialized_length(&self.entry_points) + + ToBytes::serialized_length(&self.contract_package_hash) + + ToBytes::serialized_length(&self.contract_wasm_hash) + + ToBytes::serialized_length(&self.protocol_version) + + ToBytes::serialized_length(&self.named_keys) + } +} + +impl FromBytes for Contract { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (contract_package_hash, bytes) = <[u8; KEY_HASH_LENGTH]>::from_bytes(bytes)?; + let (contract_wasm_hash, bytes) = <[u8; KEY_HASH_LENGTH]>::from_bytes(bytes)?; + let (named_keys, bytes) = NamedKeys::from_bytes(bytes)?; + let (entry_points, bytes) = EntryPoints::from_bytes(bytes)?; + let (protocol_version, bytes) = ProtocolVersion::from_bytes(bytes)?; + Ok(( + Contract { + contract_package_hash, + contract_wasm_hash, + named_keys, + entry_points, + protocol_version, + }, + bytes, + )) + } +} + +impl Default for Contract { + fn default() -> Self { + Contract { + named_keys: NamedKeys::default(), + entry_points: EntryPoints::default(), + contract_wasm_hash: [0; KEY_HASH_LENGTH], + contract_package_hash: [0; KEY_HASH_LENGTH], + protocol_version: ProtocolVersion::V1_0_0, + } + } +} + +/// Context of method execution +#[repr(u8)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum EntryPointType { + /// Runs as session code + Session = 0, + /// Runs within contract's context + Contract = 1, +} + +impl ToBytes for EntryPointType { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + (*self as u8).to_bytes() + } + + fn serialized_length(&self) -> usize { + 1 + } +} + +impl FromBytes for EntryPointType { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (value, bytes) = u8::from_bytes(bytes)?; + match value { + 0 => Ok((EntryPointType::Session, bytes)), + 1 => Ok((EntryPointType::Contract, bytes)), + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +/// Default name for an entry point +pub const DEFAULT_ENTRY_POINT_NAME: &str = "call"; + +/// Default name for an installer entry point +pub const ENTRY_POINT_NAME_INSTALL: &str = "install"; + +/// Default name for an upgrader entry point +pub const UPGRADE_ENTRY_POINT_NAME: &str = "upgrade"; + +/// Collection of entry point parameters. +pub type Parameters = Vec; + +/// Type signature of a method. Order of arguments matter since can be +/// referenced by index as well as name. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EntryPoint { + name: String, + args: Parameters, + ret: CLType, + access: EntryPointAccess, + entry_point_type: EntryPointType, +} + +impl From for (String, Parameters, CLType, EntryPointAccess, EntryPointType) { + fn from(entry_point: EntryPoint) -> Self { + ( + entry_point.name, + entry_point.args, + entry_point.ret, + entry_point.access, + entry_point.entry_point_type, + ) + } +} + +impl EntryPoint { + /// `EntryPoint` constructor. + pub fn new>( + name: T, + args: Parameters, + ret: CLType, + access: EntryPointAccess, + entry_point_type: EntryPointType, + ) -> Self { + EntryPoint { + name: name.into(), + args, + ret, + access, + entry_point_type, + } + } + + /// Create a default [`EntryPoint`] with specified name. + pub fn default_with_name>(name: T) -> Self { + EntryPoint { + name: name.into(), + ..Default::default() + } + } + + /// Get name. + pub fn name(&self) -> &str { + &self.name + } + + /// Get access enum. + pub fn access(&self) -> &EntryPointAccess { + &self.access + } + + /// Get the arguments for this method. + pub fn args(&self) -> &[Parameter] { + self.args.as_slice() + } + + /// Get the return type. + pub fn ret(&self) -> &CLType { + &self.ret + } + + /// Obtains entry point + pub fn entry_point_type(&self) -> EntryPointType { + self.entry_point_type + } +} + +impl Default for EntryPoint { + /// constructor for a public session `EntryPoint` that takes no args and returns `Unit` + fn default() -> Self { + EntryPoint { + name: DEFAULT_ENTRY_POINT_NAME.to_string(), + args: Vec::new(), + ret: CLType::Unit, + access: EntryPointAccess::Public, + entry_point_type: EntryPointType::Session, + } + } +} + +impl ToBytes for EntryPoint { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut result = bytesrepr::allocate_buffer(self)?; + result.append(&mut self.name.to_bytes()?); + result.append(&mut self.args.to_bytes()?); + self.ret.append_bytes(&mut result); + result.append(&mut self.access.to_bytes()?); + result.append(&mut self.entry_point_type.to_bytes()?); + + Ok(result) + } + + fn serialized_length(&self) -> usize { + self.name.serialized_length() + + self.args.serialized_length() + + self.ret.serialized_length() + + self.access.serialized_length() + + self.entry_point_type.serialized_length() + } +} + +impl FromBytes for EntryPoint { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (name, bytes) = String::from_bytes(bytes)?; + let (args, bytes) = Vec::::from_bytes(bytes)?; + let (ret, bytes) = CLType::from_bytes(bytes)?; + let (access, bytes) = EntryPointAccess::from_bytes(bytes)?; + let (entry_point_type, bytes) = EntryPointType::from_bytes(bytes)?; + + Ok(( + EntryPoint { + name, + args, + ret, + access, + entry_point_type, + }, + bytes, + )) + } +} + +/// Enum describing the possible access control options for a contract entry +/// point (method). +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EntryPointAccess { + /// Anyone can call this method (no access controls). + Public, + /// Only users from the listed groups may call this method. Note: if the + /// list is empty then this method is not callable from outside the + /// contract. + Groups(Vec), +} + +const ENTRYPOINTACCESS_PUBLIC_TAG: u8 = 1; +const ENTRYPOINTACCESS_GROUPS_TAG: u8 = 2; + +impl EntryPointAccess { + /// Constructor for access granted to only listed groups. + pub fn groups(labels: &[&str]) -> Self { + let list: Vec = labels.iter().map(|s| Group(String::from(*s))).collect(); + EntryPointAccess::Groups(list) + } +} + +impl ToBytes for EntryPointAccess { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut result = bytesrepr::allocate_buffer(self)?; + + match self { + EntryPointAccess::Public => { + result.push(ENTRYPOINTACCESS_PUBLIC_TAG); + } + EntryPointAccess::Groups(groups) => { + result.push(ENTRYPOINTACCESS_GROUPS_TAG); + result.append(&mut groups.to_bytes()?); + } + } + Ok(result) + } + + fn serialized_length(&self) -> usize { + match self { + EntryPointAccess::Public => 1, + EntryPointAccess::Groups(groups) => 1 + groups.serialized_length(), + } + } +} + +impl FromBytes for EntryPointAccess { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (tag, bytes) = u8::from_bytes(bytes)?; + + match tag { + ENTRYPOINTACCESS_PUBLIC_TAG => Ok((EntryPointAccess::Public, bytes)), + ENTRYPOINTACCESS_GROUPS_TAG => { + let (groups, bytes) = Vec::::from_bytes(bytes)?; + let result = EntryPointAccess::Groups(groups); + Ok((result, bytes)) + } + _ => Err(bytesrepr::Error::Formatting), + } + } +} + +/// Parameter to a method +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Parameter { + name: String, + cl_type: CLType, +} + +impl Parameter { + /// `Parameter` constructor. + pub fn new>(name: T, cl_type: CLType) -> Self { + Parameter { + name: name.into(), + cl_type, + } + } + + /// Get the type of this argument. + pub fn cl_type(&self) -> &CLType { + &self.cl_type + } +} + +impl From for (String, CLType) { + fn from(parameter: Parameter) -> Self { + (parameter.name, parameter.cl_type) + } +} + +impl ToBytes for Parameter { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut result = ToBytes::to_bytes(&self.name)?; + self.cl_type.append_bytes(&mut result); + + Ok(result) + } + + fn serialized_length(&self) -> usize { + ToBytes::serialized_length(&self.name) + self.cl_type.serialized_length() + } +} + +impl FromBytes for Parameter { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (name, bytes) = String::from_bytes(bytes)?; + let (cl_type, bytes) = CLType::from_bytes(bytes)?; + + Ok((Parameter { name, cl_type }, bytes)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{AccessRights, URef}; + use alloc::borrow::ToOwned; + + fn make_contract_package() -> ContractPackage { + let mut contract_package = ContractPackage::new( + URef::new([0; 32], AccessRights::NONE), + ContractVersions::default(), + DisabledVersions::default(), + Groups::default(), + ); + + // add groups + { + let group_urefs = { + let mut ret = BTreeSet::new(); + ret.insert(URef::new([1; 32], AccessRights::READ)); + ret + }; + + contract_package + .groups_mut() + .insert(Group::new("Group 1"), group_urefs.clone()); + + contract_package + .groups_mut() + .insert(Group::new("Group 2"), group_urefs); + } + + // add entry_points + let _entry_points = { + let mut ret = BTreeMap::new(); + let entrypoint = EntryPoint::new( + "method0".to_string(), + vec![], + CLType::U32, + EntryPointAccess::groups(&["Group 2"]), + EntryPointType::Session, + ); + ret.insert(entrypoint.name().to_owned(), entrypoint); + let entrypoint = EntryPoint::new( + "method1".to_string(), + vec![Parameter::new("Foo", CLType::U32)], + CLType::U32, + EntryPointAccess::groups(&["Group 1"]), + EntryPointType::Session, + ); + ret.insert(entrypoint.name().to_owned(), entrypoint); + ret + }; + + let _contract_package_hash = [41; 32]; + let contract_hash = [42; 32]; + let _contract_wasm_hash = [43; 32]; + let _named_keys = NamedKeys::new(); + let protocol_version = ProtocolVersion::V1_0_0; + + contract_package.insert_contract_version(protocol_version.value().major, contract_hash); + + contract_package + } + + #[test] + fn next_contract_version() { + let major = 1; + let mut contract_package = ContractPackage::new( + URef::new([0; 32], AccessRights::NONE), + ContractVersions::default(), + DisabledVersions::default(), + Groups::default(), + ); + assert_eq!(contract_package.next_contract_version_for(major), 1); + + let next_version = contract_package.insert_contract_version(major, [123; 32]); + assert_eq!(next_version, ContractVersionKey::new(major, 1)); + assert_eq!(contract_package.next_contract_version_for(major), 2); + let next_version_2 = contract_package.insert_contract_version(major, [124; 32]); + assert_eq!(next_version_2, ContractVersionKey::new(major, 2)); + + let major = 2; + assert_eq!(contract_package.next_contract_version_for(major), 1); + let next_version_3 = contract_package.insert_contract_version(major, [42; 32]); + assert_eq!(next_version_3, ContractVersionKey::new(major, 1)); + } + + #[test] + fn roundtrip_serialization() { + let contract_package = make_contract_package(); + let bytes = contract_package.to_bytes().expect("should serialize"); + let (decoded_package, rem) = + ContractPackage::from_bytes(&bytes).expect("should deserialize"); + assert_eq!(contract_package, decoded_package); + assert_eq!(rem.len(), 0); + } + + #[test] + fn should_remove_group() { + let mut contract_package = make_contract_package(); + + assert!(!contract_package.remove_group(&Group::new("Non-existent group"))); + assert!(contract_package.remove_group(&Group::new("Group 1"))); + assert!(!contract_package.remove_group(&Group::new("Group 1"))); // Group no longer exists + } + + #[test] + fn should_disable_contract_version() { + const CONTRACT_HASH: ContractHash = [123; 32]; + let mut contract_package = make_contract_package(); + + assert_eq!( + contract_package.disable_contract_version(CONTRACT_HASH), + Err(Error::ContractNotFound), + "should return contract not found error" + ); + + let next_version = contract_package.insert_contract_version(1, CONTRACT_HASH); + assert!( + contract_package.is_version_enabled(next_version), + "version should exist and be enabled" + ); + + assert_eq!( + contract_package.disable_contract_version(CONTRACT_HASH), + Ok(()), + "should be able to disable version" + ); + + assert_eq!( + contract_package.lookup_contract_hash(next_version), + None, + "should not return disabled contract version" + ); + + assert!( + !contract_package.is_version_enabled(next_version), + "version should not be enabled" + ); + } +} diff --git a/types/src/gens.rs b/types/src/gens.rs new file mode 100644 index 0000000000..8bc273d7fe --- /dev/null +++ b/types/src/gens.rs @@ -0,0 +1,329 @@ +//! Contains functions for generating arbitrary values for use by +//! [`Proptest`](https://crates.io/crates/proptest). +#![allow(missing_docs)] + +use alloc::{boxed::Box, string::String, vec}; + +use proptest::{ + array, bits, + collection::{btree_map, btree_set, vec}, + option, + prelude::*, + result, +}; + +use crate::{ + account::{AccountHash, Weight}, + contracts::{ContractVersions, DisabledVersions, Groups, NamedKeys, Parameters}, + AccessRights, CLType, CLValue, Contract, ContractPackage, ContractVersionKey, ContractWasm, + EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Group, Key, NamedArg, Parameter, + Phase, ProtocolVersion, SemVer, URef, U128, U256, U512, +}; + +pub fn u8_slice_32() -> impl Strategy { + vec(any::(), 32).prop_map(|b| { + let mut res = [0u8; 32]; + res.clone_from_slice(b.as_slice()); + res + }) +} + +pub fn named_keys_arb(depth: usize) -> impl Strategy { + btree_map("\\PC*", key_arb(), depth) +} + +pub fn access_rights_arb() -> impl Strategy { + prop_oneof![ + Just(AccessRights::NONE), + Just(AccessRights::READ), + Just(AccessRights::ADD), + Just(AccessRights::WRITE), + Just(AccessRights::READ_ADD), + Just(AccessRights::READ_WRITE), + Just(AccessRights::ADD_WRITE), + Just(AccessRights::READ_ADD_WRITE), + ] +} + +pub fn phase_arb() -> impl Strategy { + prop_oneof![ + Just(Phase::Payment), + Just(Phase::Session), + Just(Phase::FinalizePayment), + ] +} + +pub fn uref_arb() -> impl Strategy { + (array::uniform32(bits::u8::ANY), access_rights_arb()) + .prop_map(|(id, access_rights)| URef::new(id, access_rights)) +} + +pub fn key_arb() -> impl Strategy { + prop_oneof![ + account_hash_arb().prop_map(Key::Account), + u8_slice_32().prop_map(Key::Hash), + uref_arb().prop_map(Key::URef), + ] +} + +pub fn account_hash_arb() -> impl Strategy { + u8_slice_32().prop_map(AccountHash::new) +} + +pub fn weight_arb() -> impl Strategy { + any::().prop_map(Weight::new) +} + +pub fn sem_ver_arb() -> impl Strategy { + (any::(), any::(), any::()) + .prop_map(|(major, minor, patch)| SemVer::new(major, minor, patch)) +} + +pub fn protocol_version_arb() -> impl Strategy { + sem_ver_arb().prop_map(ProtocolVersion::new) +} + +pub fn u128_arb() -> impl Strategy { + vec(any::(), 0..16).prop_map(|b| U128::from_little_endian(b.as_slice())) +} + +pub fn u256_arb() -> impl Strategy { + vec(any::(), 0..32).prop_map(|b| U256::from_little_endian(b.as_slice())) +} + +pub fn u512_arb() -> impl Strategy { + vec(any::(), 0..64).prop_map(|b| U512::from_little_endian(b.as_slice())) +} + +pub fn cl_simple_type_arb() -> impl Strategy { + prop_oneof![ + Just(CLType::Bool), + Just(CLType::I32), + Just(CLType::I64), + Just(CLType::U8), + Just(CLType::U32), + Just(CLType::U64), + Just(CLType::U128), + Just(CLType::U256), + Just(CLType::U512), + Just(CLType::Unit), + Just(CLType::String), + Just(CLType::Key), + Just(CLType::URef), + ] +} + +pub fn cl_type_arb() -> impl Strategy { + cl_simple_type_arb().prop_recursive(4, 16, 8, |element| { + prop_oneof![ + // We want to produce basic types too + element.clone(), + // For complex type + element + .clone() + .prop_map(|val| CLType::Option(Box::new(val))), + element.clone().prop_map(|val| CLType::List(Box::new(val))), + // Fixed lists of any size + (element.clone(), 1u32..32u32) + .prop_map(|(cl_type, len)| CLType::FixedList(Box::new(cl_type), len)), + // Realistic Result type generator: ok is anything recursive, err is simple type + (element.clone(), cl_simple_type_arb()).prop_map(|(ok, err)| CLType::Result { + ok: Box::new(ok), + err: Box::new(err) + }), + // Realistic Map type generator: key is simple type, value is complex recursive type + (cl_simple_type_arb(), element.clone()).prop_map(|(key, value)| CLType::Map { + key: Box::new(key), + value: Box::new(value) + }), + // Various tuples + element + .clone() + .prop_map(|cl_type| CLType::Tuple1([Box::new(cl_type)])), + (element.clone(), element.clone()).prop_map(|(cl_type1, cl_type2)| CLType::Tuple2([ + Box::new(cl_type1), + Box::new(cl_type2) + ])), + (element.clone(), element.clone(), element).prop_map( + |(cl_type1, cl_type2, cl_type3)| CLType::Tuple3([ + Box::new(cl_type1), + Box::new(cl_type2), + Box::new(cl_type3) + ]) + ), + ] + }) +} + +pub fn cl_value_arb() -> impl Strategy { + // If compiler brings you here it most probably means you've added a variant to `CLType` enum + // but forgot to add generator for it. + let stub: Option = None; + if let Some(cl_type) = stub { + match cl_type { + CLType::Bool + | CLType::I32 + | CLType::I64 + | CLType::U8 + | CLType::U32 + | CLType::U64 + | CLType::U128 + | CLType::U256 + | CLType::U512 + | CLType::Unit + | CLType::String + | CLType::Key + | CLType::URef + | CLType::Option(_) + | CLType::List(_) + | CLType::FixedList(..) + | CLType::Result { .. } + | CLType::Map { .. } + | CLType::Tuple1(_) + | CLType::Tuple2(_) + | CLType::Tuple3(_) + | CLType::Any => (), + } + }; + + prop_oneof![ + Just(CLValue::from_t(()).expect("should create CLValue")), + any::().prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + any::().prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + any::().prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + any::().prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + any::().prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + any::().prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + u128_arb().prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + u256_arb().prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + u512_arb().prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + key_arb().prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + uref_arb().prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + ".*".prop_map(|x: String| CLValue::from_t(x).expect("should create CLValue")), + option::of(any::()).prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + vec(uref_arb(), 0..100).prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + [any::(); 32].prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + result::maybe_err(key_arb(), ".*") + .prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + btree_map(".*", u512_arb(), 0..100) + .prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + (any::()).prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + (any::(), any::()) + .prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + (any::(), any::(), any::()) + .prop_map(|x| CLValue::from_t(x).expect("should create CLValue")), + ] +} + +pub fn result_arb() -> impl Strategy> { + result::maybe_ok(any::(), any::()) +} + +pub fn named_args_arb() -> impl Strategy { + (".*", cl_value_arb()).prop_map(|(name, value)| NamedArg::new(name, value)) +} + +pub fn group_arb() -> impl Strategy { + ".*".prop_map(Group::new) +} + +pub fn entry_point_access_arb() -> impl Strategy { + prop_oneof![ + Just(EntryPointAccess::Public), + vec(group_arb(), 0..32).prop_map(EntryPointAccess::Groups), + ] +} + +pub fn entry_point_type_arb() -> impl Strategy { + prop_oneof![ + Just(EntryPointType::Session), + Just(EntryPointType::Contract), + ] +} + +pub fn parameter_arb() -> impl Strategy { + (".*", cl_type_arb()).prop_map(|(name, cl_type)| Parameter::new(name, cl_type)) +} + +pub fn parameters_arb() -> impl Strategy { + vec(parameter_arb(), 0..10) +} + +pub fn entry_point_arb() -> impl Strategy { + ( + ".*", + parameters_arb(), + entry_point_type_arb(), + entry_point_access_arb(), + cl_type_arb(), + ) + .prop_map( + |(name, parameters, entry_point_type, entry_point_access, ret)| { + EntryPoint::new(name, parameters, ret, entry_point_access, entry_point_type) + }, + ) +} + +pub fn entry_points_arb() -> impl Strategy { + vec(entry_point_arb(), 1..10).prop_map(EntryPoints::from) +} + +pub fn contract_arb() -> impl Strategy { + ( + protocol_version_arb(), + entry_points_arb(), + u8_slice_32(), + u8_slice_32(), + named_keys_arb(20), + ) + .prop_map( + |( + protocol_version, + entry_points, + contract_package_hash_arb, + contract_wasm_hash, + named_keys, + )| { + Contract::new( + contract_package_hash_arb, + contract_wasm_hash, + named_keys, + entry_points, + protocol_version, + ) + }, + ) +} + +pub fn contract_wasm_arb() -> impl Strategy { + vec(any::(), 1..1000).prop_map(ContractWasm::new) +} + +pub fn contract_version_key_arb() -> impl Strategy { + (1..32u32, 1..1000u32) + .prop_map(|(major, contract_ver)| ContractVersionKey::new(major, contract_ver)) +} + +pub fn contract_versions_arb() -> impl Strategy { + btree_map(contract_version_key_arb(), u8_slice_32(), 1..5) +} + +pub fn disabled_versions_arb() -> impl Strategy { + btree_set(contract_version_key_arb(), 0..5) +} + +pub fn groups_arb() -> impl Strategy { + btree_map(group_arb(), btree_set(uref_arb(), 1..10), 0..5) +} + +pub fn contract_package_arb() -> impl Strategy { + ( + uref_arb(), + contract_versions_arb(), + disabled_versions_arb(), + groups_arb(), + ) + .prop_map(|(access_key, versions, disabled_versions, groups)| { + ContractPackage::new(access_key, versions, disabled_versions, groups) + }) +} diff --git a/types/src/key.rs b/types/src/key.rs new file mode 100644 index 0000000000..30d4c249d2 --- /dev/null +++ b/types/src/key.rs @@ -0,0 +1,343 @@ +use alloc::{format, string::String, vec::Vec}; +use core::fmt::{self, Debug, Display, Formatter}; + +use hex_fmt::HexFmt; + +use crate::{ + account::AccountHash, + bytesrepr::{self, Error, FromBytes, ToBytes}, + URef, UREF_SERIALIZED_LENGTH, +}; + +const ACCOUNT_ID: u8 = 0; +const HASH_ID: u8 = 1; +const UREF_ID: u8 = 2; + +/// The number of bytes in a Blake2b hash +pub const BLAKE2B_DIGEST_LENGTH: usize = 32; +/// The number of bytes in a [`Key::Hash`]. +pub const KEY_HASH_LENGTH: usize = 32; + +const KEY_ID_SERIALIZED_LENGTH: usize = 1; +// u8 used to determine the ID +const KEY_HASH_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + KEY_HASH_LENGTH; +const KEY_UREF_SERIALIZED_LENGTH: usize = KEY_ID_SERIALIZED_LENGTH + UREF_SERIALIZED_LENGTH; + +/// An alias for [`Key`]s hash variant. +pub type HashAddr = [u8; KEY_HASH_LENGTH]; + +impl From for Key { + fn from(addr: HashAddr) -> Self { + Key::Hash(addr) + } +} + +/// An alias for [`Key`]s hash variant. +pub type ContractHash = HashAddr; +/// An alias for [`Key`]s hash variant. +pub type ContractWasmHash = HashAddr; +/// An alias for [`Key`]s hash variant. +pub type ContractPackageHash = HashAddr; + +/// The type under which data (e.g. [`CLValue`](crate::CLValue)s, smart contracts, user accounts) +/// are indexed on the network. +#[repr(C)] +#[derive(PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)] +pub enum Key { + /// A `Key` under which a user account is stored. + Account(AccountHash), + /// A `Key` under which a smart contract is stored and which is the pseudo-hash of the + /// contract. + Hash(HashAddr), + /// A `Key` which is a [`URef`], under which most types of data can be stored. + URef(URef), +} + +impl Key { + // This method is not intended to be used by third party crates. + #[doc(hidden)] + pub fn type_string(&self) -> String { + match self { + Key::Account(_) => String::from("Key::Account"), + Key::Hash(_) => String::from("Key::Hash"), + Key::URef(_) => String::from("Key::URef"), + } + } + + /// Returns the maximum size a [`Key`] can be serialized into. + pub const fn max_serialized_length() -> usize { + KEY_UREF_SERIALIZED_LENGTH + } + + /// If `self` is of type [`Key::URef`], returns `self` with the [`AccessRights`] stripped from + /// the wrapped [`URef`], otherwise returns `self` unmodified. + pub fn normalize(self) -> Key { + match self { + Key::URef(uref) => Key::URef(uref.remove_access_rights()), + other => other, + } + } + + /// Returns a human-readable version of `self`, with the inner bytes encoded to Base16. + pub fn as_string(&self) -> String { + match self { + Key::Account(account_hash) => format!( + "account-account_hash-{}", + base16::encode_lower(&account_hash.value()) + ), + Key::Hash(addr) => format!("hash-{}", base16::encode_lower(addr)), + Key::URef(uref) => uref.as_string(), + } + } + + /// Returns the inner bytes of `self` if `self` is of type [`Key::Account`], otherwise returns + /// `None`. + pub fn into_account(self) -> Option { + match self { + Key::Account(bytes) => Some(bytes), + _ => None, + } + } + + /// Returns the inner bytes of `self` if `self` is of type [`Key::Hash`], otherwise returns + /// `None`. + pub fn into_hash(self) -> Option { + match self { + Key::Hash(hash) => Some(hash), + _ => None, + } + } + + /// Returns a reference to the inner [`URef`] if `self` is of type [`Key::URef`], otherwise + /// returns `None`. + pub fn as_uref(&self) -> Option<&URef> { + match self { + Key::URef(uref) => Some(uref), + _ => None, + } + } + + /// Returns the inner [`URef`] if `self` is of type [`Key::URef`], otherwise returns `None`. + pub fn into_uref(self) -> Option { + match self { + Key::URef(uref) => Some(uref), + _ => None, + } + } + + /// Creates the seed of a local key for a context with the given base key. + pub fn into_seed(self) -> [u8; BLAKE2B_DIGEST_LENGTH] { + match self { + Key::Account(account_hash) => account_hash.value(), + Key::Hash(bytes) => bytes, + Key::URef(uref) => uref.addr(), + } + } +} + +impl Display for Key { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Key::Account(account_hash) => write!(f, "Key::Account({})", account_hash), + Key::Hash(addr) => write!(f, "Key::Hash({})", HexFmt(addr)), + Key::URef(uref) => write!(f, "Key::{}", uref), /* Display impl for URef will append */ + } + } +} + +impl Debug for Key { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl From for Key { + fn from(uref: URef) -> Key { + Key::URef(uref) + } +} + +impl From for Key { + fn from(account_hash: AccountHash) -> Key { + Key::Account(account_hash) + } +} + +impl ToBytes for Key { + fn to_bytes(&self) -> Result, Error> { + let mut result = bytesrepr::unchecked_allocate_buffer(self); + match self { + Key::Account(account_hash) => { + result.push(ACCOUNT_ID); + result.append(&mut account_hash.to_bytes()?); + } + Key::Hash(hash) => { + result.push(HASH_ID); + result.append(&mut hash.to_bytes()?); + } + Key::URef(uref) => { + result.push(UREF_ID); + result.append(&mut uref.to_bytes()?); + } + } + Ok(result) + } + + fn serialized_length(&self) -> usize { + match self { + Key::Account(account_hash) => { + KEY_ID_SERIALIZED_LENGTH + account_hash.serialized_length() + } + Key::Hash(_) => KEY_HASH_SERIALIZED_LENGTH, + Key::URef(_) => KEY_UREF_SERIALIZED_LENGTH, + } + } +} + +impl FromBytes for Key { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (id, remainder) = u8::from_bytes(bytes)?; + match id { + ACCOUNT_ID => { + let (account_hash, rem) = AccountHash::from_bytes(remainder)?; + Ok((Key::Account(account_hash), rem)) + } + HASH_ID => { + let (hash, rem) = <[u8; KEY_HASH_LENGTH]>::from_bytes(remainder)?; + Ok((Key::Hash(hash), rem)) + } + UREF_ID => { + let (uref, rem) = URef::from_bytes(remainder)?; + Ok((Key::URef(uref), rem)) + } + _ => Err(Error::Formatting), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + bytesrepr::{Error, FromBytes}, + AccessRights, URef, + }; + + fn test_readable(right: AccessRights, is_true: bool) { + assert_eq!(right.is_readable(), is_true) + } + + #[test] + fn test_is_readable() { + test_readable(AccessRights::READ, true); + test_readable(AccessRights::READ_ADD, true); + test_readable(AccessRights::READ_WRITE, true); + test_readable(AccessRights::READ_ADD_WRITE, true); + test_readable(AccessRights::ADD, false); + test_readable(AccessRights::ADD_WRITE, false); + test_readable(AccessRights::WRITE, false); + } + + fn test_writable(right: AccessRights, is_true: bool) { + assert_eq!(right.is_writeable(), is_true) + } + + #[test] + fn test_is_writable() { + test_writable(AccessRights::WRITE, true); + test_writable(AccessRights::READ_WRITE, true); + test_writable(AccessRights::ADD_WRITE, true); + test_writable(AccessRights::READ, false); + test_writable(AccessRights::ADD, false); + test_writable(AccessRights::READ_ADD, false); + test_writable(AccessRights::READ_ADD_WRITE, true); + } + + fn test_addable(right: AccessRights, is_true: bool) { + assert_eq!(right.is_addable(), is_true) + } + + #[test] + fn test_is_addable() { + test_addable(AccessRights::ADD, true); + test_addable(AccessRights::READ_ADD, true); + test_addable(AccessRights::READ_WRITE, false); + test_addable(AccessRights::ADD_WRITE, true); + test_addable(AccessRights::READ, false); + test_addable(AccessRights::WRITE, false); + test_addable(AccessRights::READ_ADD_WRITE, true); + } + + #[test] + fn should_display_key() { + let expected_hash = core::iter::repeat("0").take(64).collect::(); + let addr_array = [0u8; 32]; + let account_hash = AccountHash::new(addr_array); + let account_key = Key::Account(account_hash); + assert_eq!( + format!("{}", account_key), + format!("Key::Account({})", expected_hash) + ); + let uref_key = Key::URef(URef::new(addr_array, AccessRights::READ)); + assert_eq!( + format!("{}", uref_key), + format!("Key::URef({}, READ)", expected_hash) + ); + let hash_key = Key::Hash(addr_array); + assert_eq!( + format!("{}", hash_key), + format!("Key::Hash({})", expected_hash) + ); + } + + #[test] + fn abuse_vec_key() { + // Prefix is 2^32-1 = shouldn't allocate that much + let bytes: Vec = vec![255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let res: Result<(Vec, &[u8]), _> = FromBytes::from_bytes(&bytes); + #[cfg(target_os = "linux")] + assert_eq!(res.expect_err("should fail"), Error::OutOfMemory); + #[cfg(target_os = "macos")] + assert_eq!(res.expect_err("should fail"), Error::EarlyEndOfStream); + } + + #[test] + fn check_key_account_getters() { + let account = [42; 32]; + let account_hash = AccountHash::new(account); + let key1 = Key::Account(account_hash); + assert_eq!(key1.into_account(), Some(account_hash)); + assert!(key1.into_hash().is_none()); + assert!(key1.as_uref().is_none()); + } + + #[test] + fn check_key_hash_getters() { + let hash = [42; KEY_HASH_LENGTH]; + let key1 = Key::Hash(hash); + assert!(key1.into_account().is_none()); + assert_eq!(key1.into_hash(), Some(hash)); + assert!(key1.as_uref().is_none()); + } + + #[test] + fn check_key_uref_getters() { + let uref = URef::new([42; 32], AccessRights::READ_ADD_WRITE); + let key1 = Key::URef(uref); + assert!(key1.into_account().is_none()); + assert!(key1.into_hash().is_none()); + assert_eq!(key1.as_uref(), Some(&uref)); + } + + #[test] + fn key_max_serialized_length() { + let key_account = Key::Account(AccountHash::new([42; BLAKE2B_DIGEST_LENGTH])); + assert!(key_account.serialized_length() <= Key::max_serialized_length()); + + let key_hash = Key::Hash([42; KEY_HASH_LENGTH]); + assert!(key_hash.serialized_length() <= Key::max_serialized_length()); + + let key_uref = Key::URef(URef::new([42; BLAKE2B_DIGEST_LENGTH], AccessRights::READ)); + assert!(key_uref.serialized_length() <= Key::max_serialized_length()); + } +} diff --git a/types/src/lib.rs b/types/src/lib.rs new file mode 100644 index 0000000000..e65527e242 --- /dev/null +++ b/types/src/lib.rs @@ -0,0 +1,75 @@ +//! Types used to allow creation of Wasm contracts and tests for use on the CasperLabs Platform. +//! +//! # `no_std` +//! +//! By default, the library is `no_std`, however you can enable full `std` functionality by enabling +//! the crate's `std` feature. + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr( + not(feature = "no-unstable-features"), + feature(min_specialization, try_reserve) +)] +#![doc(html_root_url = "https://docs.rs/casperlabs-types/0.6.0")] +#![doc( + html_favicon_url = "https://raw.githubusercontent.com/CasperLabs/CasperLabs/dev/images/CasperLabs_Logo_Favicon_RGB_50px.png", + html_logo_url = "https://raw.githubusercontent.com/CasperLabs/CasperLabs/dev/images/CasperLabs_Logo_Symbol_RGB.png", + test(attr(forbid(warnings))) +)] +#![warn(missing_docs)] + +extern crate alloc; +#[cfg(any(feature = "std", test))] +#[macro_use] +extern crate std; + +mod access_rights; +pub mod account; +pub mod api_error; +mod block_time; +pub mod bytesrepr; +mod cl_type; +mod cl_value; +mod contract_wasm; +pub mod contracts; +#[cfg(any(feature = "gens", test))] +pub mod gens; +mod key; +pub mod mint; +mod phase; +pub mod proof_of_stake; +mod protocol_version; +pub mod runtime_args; +mod semver; +pub mod standard_payment; +pub mod system_contract_errors; +pub mod system_contract_type; +mod transfer_result; +mod uint; +mod uref; + +pub use crate::uint::{UIntParseError, U128, U256, U512}; +pub use access_rights::{AccessRights, ACCESS_RIGHTS_SERIALIZED_LENGTH}; +#[doc(inline)] +pub use api_error::ApiError; +pub use block_time::{BlockTime, BLOCKTIME_SERIALIZED_LENGTH}; +pub use cl_type::{named_key_type, CLType, CLTyped}; +pub use cl_value::{CLTypeMismatch, CLValue, CLValueError}; +pub use contracts::{ + Contract, ContractPackage, ContractVersion, ContractVersionKey, EntryPoint, EntryPointAccess, + EntryPointType, EntryPoints, Group, Parameter, +}; +//pub use contract_ref::ContractRef; +pub use contract_wasm::ContractWasm; +#[doc(inline)] +pub use key::{ + ContractHash, ContractPackageHash, ContractWasmHash, HashAddr, Key, BLAKE2B_DIGEST_LENGTH, + KEY_HASH_LENGTH, +}; +pub use phase::{Phase, PHASE_SERIALIZED_LENGTH}; +pub use protocol_version::{ProtocolVersion, VersionCheckResult}; +pub use runtime_args::{NamedArg, RuntimeArgs}; +pub use semver::{SemVer, SEM_VER_SERIALIZED_LENGTH}; +pub use system_contract_type::SystemContractType; +pub use transfer_result::{TransferResult, TransferredTo}; +pub use uref::{URef, UREF_ADDR_LENGTH, UREF_SERIALIZED_LENGTH}; diff --git a/types/src/mint.rs b/types/src/mint.rs new file mode 100644 index 0000000000..6b7fa6b980 --- /dev/null +++ b/types/src/mint.rs @@ -0,0 +1,72 @@ +//! Contains implementation of a Mint contract functionality. +mod runtime_provider; +mod storage_provider; + +use core::convert::TryFrom; + +use crate::{account::AccountHash, system_contract_errors::mint::Error, Key, URef, U512}; + +pub use crate::mint::{runtime_provider::RuntimeProvider, storage_provider::StorageProvider}; + +const SYSTEM_ACCOUNT: AccountHash = AccountHash::new([0; 32]); + +/// Mint trait. +pub trait Mint: RuntimeProvider + StorageProvider { + /// Mint new token with given `initial_balance` balance. Returns new purse on success, otherwise an error. + fn mint(&mut self, initial_balance: U512) -> Result { + let caller = self.get_caller(); + if !initial_balance.is_zero() && caller != SYSTEM_ACCOUNT { + return Err(Error::InvalidNonEmptyPurseCreation); + } + + let balance_key: Key = self.new_uref(initial_balance).into(); + let purse_uref: URef = self.new_uref(()); + let purse_uref_name = purse_uref.remove_access_rights().as_string(); + + // store balance uref so that the runtime knows the mint has full access + self.put_key(&purse_uref_name, balance_key); + + // store association between purse id and balance uref + self.write_local(purse_uref.addr(), balance_key); + // self.write(purse_uref.addr(), Key::Hash) + + Ok(purse_uref) + } + + /// Read balance of given `purse`. + fn balance(&mut self, purse: URef) -> Result, Error> { + let balance_uref: URef = match self.read_local(&purse.addr())? { + Some(key) => TryFrom::::try_from(key).map_err(|_| Error::InvalidAccessRights)?, + None => return Ok(None), + }; + match self.read(balance_uref)? { + some @ Some(_) => Ok(some), + None => Err(Error::PurseNotFound), + } + } + + /// Transfers `amount` of tokens from `source` purse to a `target` purse. + fn transfer(&mut self, source: URef, target: URef, amount: U512) -> Result<(), Error> { + if !source.is_writeable() || !target.is_addable() { + return Err(Error::InvalidAccessRights); + } + let source_balance: URef = match self.read_local(&source.addr())? { + Some(key) => TryFrom::::try_from(key).map_err(|_| Error::InvalidAccessRights)?, + None => return Err(Error::SourceNotFound), + }; + let source_value: U512 = match self.read(source_balance)? { + Some(source_value) => source_value, + None => return Err(Error::SourceNotFound), + }; + if amount > source_value { + return Err(Error::InsufficientFunds); + } + let target_balance: URef = match self.read_local(&target.addr())? { + Some(key) => TryFrom::::try_from(key).map_err(|_| Error::InvalidAccessRights)?, + None => return Err(Error::DestNotFound), + }; + self.write(source_balance, source_value - amount)?; + self.add(target_balance, amount)?; + Ok(()) + } +} diff --git a/types/src/mint/runtime_provider.rs b/types/src/mint/runtime_provider.rs new file mode 100644 index 0000000000..34f5bdbbea --- /dev/null +++ b/types/src/mint/runtime_provider.rs @@ -0,0 +1,10 @@ +use crate::{account::AccountHash, Key}; + +/// Provider of runtime host functionality. +pub trait RuntimeProvider { + /// This method should return the caller of the current context. + fn get_caller(&self) -> AccountHash; + + /// This method should handle storing given [`Key`] under `name`. + fn put_key(&mut self, name: &str, key: Key); +} diff --git a/types/src/mint/storage_provider.rs b/types/src/mint/storage_provider.rs new file mode 100644 index 0000000000..892c80508b --- /dev/null +++ b/types/src/mint/storage_provider.rs @@ -0,0 +1,29 @@ +use crate::{ + bytesrepr::{FromBytes, ToBytes}, + system_contract_errors::mint::Error, + CLTyped, URef, +}; + +/// Provides functionality of a contract storage. +pub trait StorageProvider { + /// Create new [`URef`]. + fn new_uref(&mut self, init: T) -> URef; + + /// Write data to a local key. + fn write_local(&mut self, key: K, value: V); + + /// Read data from a local key. + fn read_local( + &mut self, + key: &K, + ) -> Result, Error>; + + /// Read data from [`URef`]. + fn read(&mut self, uref: URef) -> Result, Error>; + + /// Write data under a [`URef`]. + fn write(&mut self, uref: URef, value: T) -> Result<(), Error>; + + /// Add data to a [`URef`]. + fn add(&mut self, uref: URef, value: T) -> Result<(), Error>; +} diff --git a/types/src/phase.rs b/types/src/phase.rs new file mode 100644 index 0000000000..044a9586df --- /dev/null +++ b/types/src/phase.rs @@ -0,0 +1,55 @@ +// Can be removed once https://github.com/rust-lang/rustfmt/issues/3362 is resolved. +#[rustfmt::skip] +use alloc::vec; +use alloc::vec::Vec; + +use num_derive::{FromPrimitive, ToPrimitive}; +use num_traits::{FromPrimitive, ToPrimitive}; + +use crate::{ + bytesrepr::{Error, FromBytes, ToBytes}, + CLType, CLTyped, +}; + +/// The number of bytes in a serialized [`Phase`]. +pub const PHASE_SERIALIZED_LENGTH: usize = 1; + +/// The phase in which a given contract is executing. +#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive, ToPrimitive)] +#[repr(u8)] +pub enum Phase { + /// Set while committing the genesis or upgrade configurations. + System = 0, + /// Set while executing the payment code of a deploy. + Payment = 1, + /// Set while executing the session code of a deploy. + Session = 2, + /// Set while finalizing payment at the end of a deploy. + FinalizePayment = 3, +} + +impl ToBytes for Phase { + fn to_bytes(&self) -> Result, Error> { + let id = self.to_u8().expect("Phase is represented as a u8"); + + Ok(vec![id]) + } + + fn serialized_length(&self) -> usize { + PHASE_SERIALIZED_LENGTH + } +} + +impl FromBytes for Phase { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (id, rest) = u8::from_bytes(bytes)?; + let phase = FromPrimitive::from_u8(id).ok_or(Error::Formatting)?; + Ok((phase, rest)) + } +} + +impl CLTyped for Phase { + fn cl_type() -> CLType { + CLType::U8 + } +} diff --git a/types/src/proof_of_stake.rs b/types/src/proof_of_stake.rs new file mode 100644 index 0000000000..9c83026e35 --- /dev/null +++ b/types/src/proof_of_stake.rs @@ -0,0 +1,426 @@ +//! Contains implementation of a Proof Of Stake contract functionality. +mod mint_provider; +mod queue; +mod queue_provider; +mod runtime_provider; +mod stakes; +mod stakes_provider; + +use core::marker::Sized; + +use crate::{ + account::AccountHash, + system_contract_errors::pos::{Error, Result}, + AccessRights, TransferredTo, URef, U512, +}; + +pub use crate::proof_of_stake::{ + mint_provider::MintProvider, queue::Queue, queue_provider::QueueProvider, + runtime_provider::RuntimeProvider, stakes::Stakes, stakes_provider::StakesProvider, +}; + +/// Proof of stake functionality implementation. +pub trait ProofOfStake: + MintProvider + QueueProvider + RuntimeProvider + StakesProvider + Sized +{ + /// Bonds a `validator` with `amount` of tokens from a `source` purse. + fn bond(&mut self, validator: AccountHash, amount: U512, source: URef) -> Result<()> { + if amount.is_zero() { + return Err(Error::BondTooSmall); + } + let target = internal::get_bonding_purse(self)?; + let timestamp = self.get_block_time(); + // Transfer `amount` from the `source` purse to PoS internal purse. POS_PURSE is a constant, + // it is the URef of the proof-of-stake contract's own purse. + + self.transfer_purse_to_purse(source, target, amount) + .map_err(|_| Error::BondTransferFailed)?; + internal::bond(self, amount, validator, timestamp)?; + + // TODO: Remove this and set nonzero delays once the system calls `step` in each block. + let unbonds = internal::step(self, timestamp)?; + for entry in unbonds { + let _: TransferredTo = self + .transfer_purse_to_account(source, entry.validator, entry.amount) + .map_err(|_| Error::BondTransferFailed)?; + } + Ok(()) + } + + /// Unbonds a validator with provided `maybe_amount`. If `maybe_amount` is `None` then given validator will withdraw all funds. + fn unbond(&mut self, validator: AccountHash, maybe_amount: Option) -> Result<()> { + let pos_purse = internal::get_bonding_purse(self)?; + let timestamp = self.get_block_time(); + internal::unbond(self, maybe_amount, validator, timestamp)?; + + // TODO: Remove this and set nonzero delays once the system calls `step` in each block. + let unbonds = internal::step(self, timestamp)?; + for entry in unbonds { + self.transfer_purse_to_account(pos_purse, entry.validator, entry.amount) + .map_err(|_| Error::UnbondTransferFailed)?; + } + Ok(()) + } + + /// Get payment purse. + fn get_payment_purse(&self) -> Result { + let purse = internal::get_payment_purse(self)?; + // Limit the access rights so only balance query and deposit are allowed. + Ok(URef::new(purse.addr(), AccessRights::READ_ADD)) + } + + /// Set refund purse. + fn set_refund_purse(&mut self, purse: URef) -> Result<()> { + internal::set_refund(self, purse) + } + + /// Get refund purse. + fn get_refund_purse(&self) -> Result> { + // We purposely choose to remove the access rights so that we do not + // accidentally give rights for a purse to some contract that is not + // supposed to have it. + let maybe_purse = internal::get_refund_purse(self)?; + Ok(maybe_purse.map(|p| p.remove_access_rights())) + } + + /// Finalize payment with `amount_spent` and a given `account`. + fn finalize_payment(&mut self, amount_spent: U512, account: AccountHash) -> Result<()> { + internal::finalize_payment(self, amount_spent, account) + } +} + +mod internal { + use alloc::vec::Vec; + + use crate::{ + account::AccountHash, + system_contract_errors::pos::{Error, PurseLookupError, Result}, + BlockTime, Key, Phase, URef, U512, + }; + + use crate::proof_of_stake::{ + mint_provider::MintProvider, queue::QueueEntry, queue_provider::QueueProvider, + runtime_provider::RuntimeProvider, stakes_provider::StakesProvider, + }; + + /// Account used to run system functions (in particular `finalize_payment`). + const SYSTEM_ACCOUNT: AccountHash = AccountHash::new([0u8; 32]); + + /// The uref name where the PoS purse is stored. It contains all staked motes, and all unbonded + /// motes that are yet to be paid out. + const BONDING_PURSE_KEY: &str = "pos_bonding_purse"; + + /// The uref name where the PoS accepts payment for computation on behalf of validators. + const PAYMENT_PURSE_KEY: &str = "pos_payment_purse"; + + /// The uref name where the PoS holds validator earnings before distributing them. + const REWARDS_PURSE_KEY: &str = "pos_rewards_purse"; + + /// The uref name where the PoS will refund unused payment back to the user. The uref this name + /// corresponds to is set by the user. + const REFUND_PURSE_KEY: &str = "pos_refund_purse"; + + /// The time from a bonding request until the bond becomes effective and part of the stake. + const BOND_DELAY: u64 = 0; + + /// The time from an unbonding request until the stakes are paid out. + const UNBOND_DELAY: u64 = 0; + + /// The maximum number of pending bonding requests. + const MAX_BOND_LEN: usize = 100; + + /// The maximum number of pending unbonding requests. + const MAX_UNBOND_LEN: usize = 1000; + + /// Enqueues the deploy's creator for becoming a validator. The bond `amount` is paid from the + /// purse `source`. + pub fn bond( + provider: &mut P, + amount: U512, + validator: AccountHash, + timestamp: BlockTime, + ) -> Result<()> { + let mut queue = provider.read_bonding(); + if queue.0.len() >= MAX_BOND_LEN { + return Err(Error::TooManyEventsInQueue); + } + + let mut stakes = provider.read()?; + // Simulate applying all earlier bonds. The modified stakes are not written. + for entry in &queue.0 { + stakes.bond(&entry.validator, entry.amount); + } + stakes.validate_bonding(&validator, amount)?; + + queue.push(validator, amount, timestamp)?; + provider.write_bonding(queue); + Ok(()) + } + + /// Enqueues the deploy's creator for unbonding. Their vote weight as a validator is decreased + /// immediately, but the funds will only be released after a delay. If `maybe_amount` is `None`, + /// all funds are enqueued for withdrawal, terminating the validator status. + pub fn unbond( + provider: &mut P, + maybe_amount: Option, + validator: AccountHash, + timestamp: BlockTime, + ) -> Result<()> { + let mut queue = provider.read_unbonding(); + if queue.0.len() >= MAX_UNBOND_LEN { + return Err(Error::TooManyEventsInQueue); + } + + let mut stakes = provider.read()?; + let payout = stakes.unbond(&validator, maybe_amount)?; + provider.write(&stakes); + // TODO: Make sure the destination is valid and the amount can be paid. The actual payment + // will be made later, after the unbonding delay. contract_api::transfer_dry_run(POS_PURSE, + // dest, amount)?; + queue.push(validator, payout, timestamp)?; + provider.write_unbonding(queue); + Ok(()) + } + + /// Removes all due requests from the queues and applies them. + pub fn step( + provider: &mut P, + timestamp: BlockTime, + ) -> Result> { + let mut bonding_queue = provider.read_bonding(); + let mut unbonding_queue = provider.read_unbonding(); + + let bonds = bonding_queue.pop_due(timestamp.saturating_sub(BlockTime::new(BOND_DELAY))); + let unbonds = + unbonding_queue.pop_due(timestamp.saturating_sub(BlockTime::new(UNBOND_DELAY))); + + if !unbonds.is_empty() { + provider.write_unbonding(unbonding_queue); + } + + if !bonds.is_empty() { + provider.write_bonding(bonding_queue); + let mut stakes = provider.read()?; + for entry in bonds { + stakes.bond(&entry.validator, entry.amount); + } + provider.write(&stakes); + } + + Ok(unbonds) + } + + /// Attempts to look up a purse from the named_keys + fn get_purse( + runtime_provider: &R, + name: &str, + ) -> core::result::Result { + runtime_provider + .get_key(name) + .ok_or(PurseLookupError::KeyNotFound) + .and_then(|key| match key { + Key::URef(uref) => Ok(uref), + _ => Err(PurseLookupError::KeyUnexpectedType), + }) + } + + /// Returns the purse for accepting payment for transactions. + pub fn get_payment_purse(runtime_provider: &R) -> Result { + get_purse::(runtime_provider, PAYMENT_PURSE_KEY).map_err(PurseLookupError::payment) + } + + /// Returns the purse for holding bonds + pub fn get_bonding_purse(runtime_provider: &R) -> Result { + get_purse::(runtime_provider, BONDING_PURSE_KEY).map_err(PurseLookupError::bonding) + } + + /// Returns the purse for holding validator earnings + pub fn get_rewards_purse(runtime_provider: &R) -> Result { + get_purse::(runtime_provider, REWARDS_PURSE_KEY).map_err(PurseLookupError::rewards) + } + + /// Sets the purse where refunds (excess funds not spent to pay for computation) will be sent. + /// Note that if this function is never called, the default location is the main purse of the + /// deployer's account. + pub fn set_refund(runtime_provider: &mut R, purse: URef) -> Result<()> { + if let Phase::Payment = runtime_provider.get_phase() { + runtime_provider.put_key(REFUND_PURSE_KEY, Key::URef(purse)); + return Ok(()); + } + Err(Error::SetRefundPurseCalledOutsidePayment) + } + + /// Returns the currently set refund purse. + pub fn get_refund_purse(runtime_provider: &R) -> Result> { + match get_purse::(runtime_provider, REFUND_PURSE_KEY) { + Ok(uref) => Ok(Some(uref)), + Err(PurseLookupError::KeyNotFound) => Ok(None), + Err(PurseLookupError::KeyUnexpectedType) => Err(Error::RefundPurseKeyUnexpectedType), + } + } + + /// Transfers funds from the payment purse to the validator rewards purse, as well as to the + /// refund purse, depending on how much was spent on the computation. This function maintains + /// the invariant that the balance of the payment purse is zero at the beginning and end of each + /// deploy and that the refund purse is unset at the beginning and end of each deploy. + pub fn finalize_payment( + provider: &mut P, + amount_spent: U512, + account: AccountHash, + ) -> Result<()> { + let caller = provider.get_caller(); + if caller != SYSTEM_ACCOUNT { + return Err(Error::SystemFunctionCalledByUserAccount); + } + + let payment_purse = get_payment_purse(provider)?; + let total = match provider.balance(payment_purse) { + Some(balance) => balance, + None => return Err(Error::PaymentPurseBalanceNotFound), + }; + + if total < amount_spent { + return Err(Error::InsufficientPaymentForAmountSpent); + } + let refund_amount = total - amount_spent; + + let rewards_purse = get_rewards_purse(provider)?; + let refund_purse = get_refund_purse(provider)?; + provider.remove_key(REFUND_PURSE_KEY); //unset refund purse after reading it + + // pay validators + provider + .transfer_purse_to_purse(payment_purse, rewards_purse, amount_spent) + .map_err(|_| Error::FailedTransferToRewardsPurse)?; + + if refund_amount.is_zero() { + return Ok(()); + } + + // give refund + let refund_purse = match refund_purse { + Some(uref) => uref, + None => return refund_to_account::

(provider, payment_purse, account, refund_amount), + }; + + // in case of failure to transfer to refund purse we fall back on the account's main purse + if provider + .transfer_purse_to_purse(payment_purse, refund_purse, refund_amount) + .is_err() + { + return refund_to_account::

(provider, payment_purse, account, refund_amount); + } + + Ok(()) + } + + pub fn refund_to_account( + mint_provider: &mut M, + payment_purse: URef, + account: AccountHash, + amount: U512, + ) -> Result<()> { + match mint_provider.transfer_purse_to_account(payment_purse, account, amount) { + Ok(_) => Ok(()), + Err(_) => Err(Error::FailedTransferToAccountPurse), + } + } + + #[cfg(test)] + mod tests { + extern crate std; + + use std::{cell::RefCell, iter, thread_local}; + + use crate::{account::AccountHash, system_contract_errors::pos::Result, BlockTime, U512}; + + use super::{bond, step, unbond, BOND_DELAY, UNBOND_DELAY}; + use crate::proof_of_stake::{ + queue::Queue, queue_provider::QueueProvider, stakes::Stakes, + stakes_provider::StakesProvider, + }; + + const KEY1: [u8; 32] = [1; 32]; + const KEY2: [u8; 32] = [2; 32]; + + thread_local! { + static BONDING: RefCell = RefCell::new(Queue(Default::default())); + static UNBONDING: RefCell = RefCell::new(Queue(Default::default())); + static STAKES: RefCell = RefCell::new( + Stakes(iter::once((AccountHash::new(KEY1), U512::from(1_000))).collect()) + ); + } + + struct Provider; + + impl QueueProvider for Provider { + fn read_bonding(&mut self) -> Queue { + BONDING.with(|b| b.borrow().clone()) + } + + fn read_unbonding(&mut self) -> Queue { + UNBONDING.with(|ub| ub.borrow().clone()) + } + + fn write_bonding(&mut self, queue: Queue) { + BONDING.with(|b| b.replace(queue)); + } + + fn write_unbonding(&mut self, queue: Queue) { + UNBONDING.with(|ub| ub.replace(queue)); + } + } + + impl StakesProvider for Provider { + fn read(&self) -> Result { + STAKES.with(|s| Ok(s.borrow().clone())) + } + + fn write(&mut self, stakes: &Stakes) { + STAKES.with(|s| s.replace(stakes.clone())); + } + } + + fn assert_stakes(stakes: &[([u8; 32], usize)]) { + let expected = Stakes( + stakes + .iter() + .map(|(key, amount)| (AccountHash::new(*key), U512::from(*amount))) + .collect(), + ); + assert_eq!(Ok(expected), Provider.read()); + } + + #[test] + fn test_bond_step_unbond() { + let mut provider = Provider; + bond( + &mut provider, + U512::from(500), + AccountHash::new(KEY2), + BlockTime::new(1), + ) + .expect("bond validator 2"); + + // Bonding becomes effective only after the delay. + assert_stakes(&[(KEY1, 1_000)]); + step(&mut provider, BlockTime::new(BOND_DELAY)).expect("step 1"); + assert_stakes(&[(KEY1, 1_000)]); + step(&mut provider, BlockTime::new(1 + BOND_DELAY)).expect("step 2"); + assert_stakes(&[(KEY1, 1_000), (KEY2, 500)]); + + unbond::( + &mut provider, + Some(U512::from(500)), + AccountHash::new(KEY1), + BlockTime::new(2), + ) + .expect("partly unbond validator 1"); + + // Unbonding becomes effective immediately. + assert_stakes(&[(KEY1, 500), (KEY2, 500)]); + step::(&mut provider, BlockTime::new(2 + UNBOND_DELAY)).expect("step 3"); + assert_stakes(&[(KEY1, 500), (KEY2, 500)]); + } + } +} diff --git a/types/src/proof_of_stake/mint_provider.rs b/types/src/proof_of_stake/mint_provider.rs new file mode 100644 index 0000000000..1a659f3947 --- /dev/null +++ b/types/src/proof_of_stake/mint_provider.rs @@ -0,0 +1,23 @@ +use crate::{account::AccountHash, TransferResult, URef, U512}; + +/// Provides an access to mint. +pub trait MintProvider { + /// Transfer `amount` from `source` purse to a `target` account. + fn transfer_purse_to_account( + &mut self, + source: URef, + target: AccountHash, + amount: U512, + ) -> TransferResult; + + /// Transfer `amount` from `source` purse to a `target` purse. + fn transfer_purse_to_purse( + &mut self, + source: URef, + target: URef, + amount: U512, + ) -> Result<(), ()>; + + /// Checks balance of a `purse`. Returns `None` if given purse does not exist. + fn balance(&mut self, purse: URef) -> Option; +} diff --git a/types/src/proof_of_stake/queue.rs b/types/src/proof_of_stake/queue.rs new file mode 100644 index 0000000000..b8be94b0b2 --- /dev/null +++ b/types/src/proof_of_stake/queue.rs @@ -0,0 +1,205 @@ +use alloc::{boxed::Box, vec::Vec}; +use core::result; + +use crate::{ + account::AccountHash, + bytesrepr::{self, FromBytes, ToBytes, U64_SERIALIZED_LENGTH}, + system_contract_errors::pos::{Error, Result}, + BlockTime, CLType, CLTyped, U512, +}; + +/// A pending entry in the bonding or unbonding queue. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct QueueEntry { + /// The validator who is bonding or unbonding. + pub validator: AccountHash, + /// The amount by which to change the stakes. + pub amount: U512, + /// The timestamp when the request was made. + pub timestamp: BlockTime, +} + +impl QueueEntry { + /// Creates a new `QueueEntry` with the current block's timestamp. + fn new(validator: AccountHash, amount: U512, timestamp: BlockTime) -> QueueEntry { + QueueEntry { + validator, + amount, + timestamp, + } + } +} + +impl ToBytes for QueueEntry { + fn to_bytes(&self) -> result::Result, bytesrepr::Error> { + let mut bytes = bytesrepr::allocate_buffer(self)?; + bytes.append(&mut self.validator.to_bytes()?); + bytes.append(&mut self.amount.to_bytes()?); + bytes.append(&mut self.timestamp.to_bytes()?); + Ok(bytes) + } + + fn serialized_length(&self) -> usize { + self.validator.serialized_length() + + self.amount.serialized_length() + + self.timestamp.serialized_length() + } +} + +impl FromBytes for QueueEntry { + fn from_bytes(bytes: &[u8]) -> result::Result<(Self, &[u8]), bytesrepr::Error> { + let (validator, bytes) = AccountHash::from_bytes(bytes)?; + let (amount, bytes) = U512::from_bytes(bytes)?; + let (timestamp, bytes) = BlockTime::from_bytes(bytes)?; + let entry = QueueEntry { + validator, + amount, + timestamp, + }; + Ok((entry, bytes)) + } +} + +impl CLTyped for QueueEntry { + fn cl_type() -> CLType { + CLType::Any + } +} + +/// A queue of bonding or unbonding requests, sorted by timestamp in ascending order. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Queue(pub Vec); + +impl Queue { + /// Pushes a new entry to the end of the queue. + /// + /// Returns an error if the validator already has a request in the queue. + pub fn push( + &mut self, + validator: AccountHash, + amount: U512, + timestamp: BlockTime, + ) -> Result<()> { + if self.0.iter().any(|entry| entry.validator == validator) { + return Err(Error::MultipleRequests); + } + if let Some(entry) = self.0.last() { + if entry.timestamp > timestamp { + return Err(Error::TimeWentBackwards); + } + } + self.0.push(QueueEntry::new(validator, amount, timestamp)); + Ok(()) + } + + /// Returns all queue entries at least as old as the specified timestamp. + pub fn pop_due(&mut self, timestamp: BlockTime) -> Vec { + let (older_than, rest) = self + .0 + .iter() + .partition(|entry| entry.timestamp <= timestamp); + self.0 = rest; + older_than + } +} + +impl ToBytes for Queue { + fn to_bytes(&self) -> result::Result, bytesrepr::Error> { + let mut bytes = bytesrepr::allocate_buffer(self)?; + bytes.append(&mut (self.0.len() as u64).to_bytes()?); + for entry in &self.0 { + bytes.append(&mut entry.to_bytes()?); + } + Ok(bytes) + } + + fn serialized_length(&self) -> usize { + U64_SERIALIZED_LENGTH + self.0.iter().map(ToBytes::serialized_length).sum::() + } +} + +impl FromBytes for Queue { + fn from_bytes(bytes: &[u8]) -> result::Result<(Self, &[u8]), bytesrepr::Error> { + let (len, mut bytes) = u64::from_bytes(bytes)?; + let mut queue = Vec::new(); + for _ in 0..len { + let (entry, rest) = QueueEntry::from_bytes(bytes)?; + bytes = rest; + queue.push(entry); + } + Ok((Queue(queue), bytes)) + } +} + +impl CLTyped for Queue { + fn cl_type() -> CLType { + CLType::List(Box::new(QueueEntry::cl_type())) + } +} + +#[cfg(test)] +mod tests { + use alloc::vec; + + use crate::{ + account::AccountHash, bytesrepr, system_contract_errors::pos::Error, BlockTime, U512, + }; + + use super::{Queue, QueueEntry}; + + const KEY1: [u8; 32] = [1; 32]; + const KEY2: [u8; 32] = [2; 32]; + const KEY3: [u8; 32] = [3; 32]; + + #[test] + fn test_push() { + let val1 = AccountHash::new(KEY1); + let val2 = AccountHash::new(KEY2); + let val3 = AccountHash::new(KEY3); + let mut queue: Queue = Default::default(); + assert_eq!(Ok(()), queue.push(val1, U512::from(5), BlockTime::new(100))); + assert_eq!(Ok(()), queue.push(val2, U512::from(5), BlockTime::new(101))); + assert_eq!( + Err(Error::MultipleRequests), + queue.push(val1, U512::from(5), BlockTime::new(102)) + ); + assert_eq!( + Err(Error::TimeWentBackwards), + queue.push(val3, U512::from(5), BlockTime::new(100)) + ); + } + + #[test] + fn test_pop_due() { + let val1 = AccountHash::new(KEY1); + let val2 = AccountHash::new(KEY2); + let val3 = AccountHash::new(KEY3); + let mut queue: Queue = Default::default(); + assert_eq!(Ok(()), queue.push(val1, U512::from(5), BlockTime::new(100))); + assert_eq!(Ok(()), queue.push(val2, U512::from(6), BlockTime::new(101))); + assert_eq!(Ok(()), queue.push(val3, U512::from(7), BlockTime::new(102))); + assert_eq!( + vec![ + QueueEntry::new(val1, U512::from(5), BlockTime::new(100)), + QueueEntry::new(val2, U512::from(6), BlockTime::new(101)), + ], + queue.pop_due(BlockTime::new(101)) + ); + assert_eq!( + vec![QueueEntry::new(val3, U512::from(7), BlockTime::new(102)),], + queue.pop_due(BlockTime::new(105)) + ); + } + + #[test] + fn serialization_roundtrip() { + let val1 = AccountHash::new(KEY1); + let val2 = AccountHash::new(KEY2); + let val3 = AccountHash::new(KEY3); + let mut queue: Queue = Default::default(); + queue.push(val1, U512::from(5), BlockTime::new(0)).unwrap(); + queue.push(val2, U512::from(6), BlockTime::new(1)).unwrap(); + queue.push(val3, U512::from(7), BlockTime::new(2)).unwrap(); + bytesrepr::test_serialization_roundtrip(&queue); + } +} diff --git a/types/src/proof_of_stake/queue_provider.rs b/types/src/proof_of_stake/queue_provider.rs new file mode 100644 index 0000000000..28e20216a0 --- /dev/null +++ b/types/src/proof_of_stake/queue_provider.rs @@ -0,0 +1,16 @@ +use crate::proof_of_stake::queue::Queue; + +/// Provider of a [`Queue`] access methods. +pub trait QueueProvider { + /// Reads bonding queue. + fn read_bonding(&mut self) -> Queue; + + /// Reads unbonding queue. + fn read_unbonding(&mut self) -> Queue; + + /// Writes bonding queue. + fn write_bonding(&mut self, queue: Queue); + + /// Writes unbonding queue. + fn write_unbonding(&mut self, queue: Queue); +} diff --git a/types/src/proof_of_stake/runtime_provider.rs b/types/src/proof_of_stake/runtime_provider.rs new file mode 100644 index 0000000000..45b4252c39 --- /dev/null +++ b/types/src/proof_of_stake/runtime_provider.rs @@ -0,0 +1,22 @@ +use crate::{account::AccountHash, BlockTime, Key, Phase}; + +/// Provider of runtime host functionality. +pub trait RuntimeProvider { + /// Get named key under a `name`. + fn get_key(&self, name: &str) -> Option; + + /// Put key under a `name`. + fn put_key(&mut self, name: &str, key: Key); + + /// Remove a named key by `name`. + fn remove_key(&mut self, name: &str); + + /// Get current execution phase. + fn get_phase(&self) -> Phase; + + /// Get current block time. + fn get_block_time(&self) -> BlockTime; + + /// Get caller. + fn get_caller(&self) -> AccountHash; +} diff --git a/types/src/proof_of_stake/stakes.rs b/types/src/proof_of_stake/stakes.rs new file mode 100644 index 0000000000..611413c550 --- /dev/null +++ b/types/src/proof_of_stake/stakes.rs @@ -0,0 +1,288 @@ +use alloc::{ + collections::{ + btree_map::{Iter, Values}, + BTreeMap, + }, + format, + string::String, +}; + +use crate::{ + account::AccountHash, + system_contract_errors::pos::{Error, Result}, + U512, +}; + +/// The maximum difference between the largest and the smallest stakes. +// TODO: Should this be a percentage instead? +// TODO: Pick a reasonable value. +const MAX_SPREAD: U512 = U512::MAX; + +/// The maximum increase of stakes in a single bonding request. +const MAX_INCREASE: U512 = U512::MAX; + +/// The maximum decrease of stakes in a single unbonding request. +const MAX_DECREASE: U512 = U512::MAX; + +/// The maximum increase of stakes in millionths of the total stakes in a single bonding request. +const MAX_REL_INCREASE: u64 = 1_000_000_000; + +/// The maximum decrease of stakes in millionths of the total stakes in a single unbonding request. +const MAX_REL_DECREASE: u64 = 900_000; + +/// The stakes map, assigning the staked amount of motes to each bonded +/// validator. +#[derive(Clone, Debug, PartialEq)] +pub struct Stakes(pub BTreeMap); + +impl Stakes { + /// Create new stakes map. + pub fn new(map: BTreeMap) -> Stakes { + Stakes(map) + } + + /// Return an iterator to all items in stakes map. + pub fn iter(&self) -> Iter { + self.0.iter() + } + + /// Return an iterator to values of a stakes map. + pub fn values(&self) -> Values { + self.0.values() + } + + /// Return an iterator that encodes all entries as strings. + pub fn strings(&self) -> impl Iterator + '_ { + self.iter().map(|(account_hash, balance)| { + let key_bytes = account_hash.as_bytes(); + let hex_key = base16::encode_lower(&key_bytes); + format!("v_{}_{}", hex_key, balance) + }) + } + + /// Returns total amount of bonds in a stakes map. + pub fn total_bonds(&self) -> U512 { + self.values().fold(U512::zero(), |x, y| x + y) + } + + /// If `maybe_amount` is `None`, removes all the validator's stakes, + /// otherwise subtracts the given amount. If the stakes are lower than + /// the specified amount, it also subtracts all the stakes. + /// + /// Returns the amount that was actually subtracted from the stakes, or an + /// error if + /// * unbonding the specified amount is not allowed, + /// * tries to unbond last validator, + /// * validator was not bonded. + pub fn unbond(&mut self, validator: &AccountHash, maybe_amount: Option) -> Result { + let min = self + .max_without(validator) + .unwrap_or_else(U512::zero) + .saturating_sub(MAX_SPREAD); + let max_decrease = MAX_DECREASE.min(self.sum() * MAX_REL_DECREASE / 1_000_000); + + if let Some(amount) = maybe_amount { + // The minimum stake value to not violate the maximum spread. + let stake = self.0.get_mut(validator).ok_or(Error::NotBonded)?; + if *stake > amount { + if *stake - amount < min { + return Err(Error::SpreadTooHigh); + } + if amount > max_decrease { + return Err(Error::UnbondTooLarge); + } + *stake -= amount; + return Ok(amount); + } + } + if self.0.len() == 1 { + return Err(Error::CannotUnbondLastValidator); + } + + // If the the amount is greater or equal to the stake, remove the validator. + let stake = self.0.remove(validator).ok_or(Error::NotBonded)?; + + if let Some(amount) = maybe_amount { + if amount > stake { + return Err(Error::UnbondTooLarge); + } + } + + if stake > min.saturating_add(max_decrease) && stake > max_decrease { + return Err(Error::UnbondTooLarge); + } + Ok(stake) + } + + /// Adds `amount` to the validator's stakes. + pub fn bond(&mut self, validator: &AccountHash, amount: U512) { + self.0 + .entry(*validator) + .and_modify(|x| *x += amount) + .or_insert(amount); + } + + /// Returns an error if bonding the specified amount is not allowed. + pub fn validate_bonding(&self, validator: &AccountHash, amount: U512) -> Result<()> { + let max = self + .min_without(validator) + .unwrap_or(U512::MAX) + .saturating_add(MAX_SPREAD); + let min = self + .max_without(validator) + .unwrap_or_else(U512::zero) + .saturating_sub(MAX_SPREAD); + let stake = self.0.get(validator).map(|s| *s + amount).unwrap_or(amount); + if stake > max || stake < min { + return Err(Error::SpreadTooHigh); + } + let max_increase = MAX_INCREASE.min(self.sum() * MAX_REL_INCREASE / 1_000_000); + if (stake.is_zero() && amount > min.saturating_add(max_increase)) + || (!stake.is_zero() && amount > max_increase) + { + return Err(Error::BondTooLarge); + } + Ok(()) + } + + /// Returns the minimum stake of the _other_ validators. + fn min_without(&self, validator: &AccountHash) -> Option { + self.0 + .iter() + .filter(|(v, _)| *v != validator) + .map(|(_, s)| s) + .min() + .cloned() + } + + /// Returns the maximum stake of the _other_ validators. + fn max_without(&self, validator: &AccountHash) -> Option { + self.0 + .iter() + .filter(|(v, _)| *v != validator) + .map(|(_, s)| s) + .max() + .cloned() + } + + /// Returns the total stakes. + fn sum(&self) -> U512 { + self.0 + .values() + .fold(U512::zero(), |sum, s| sum.saturating_add(*s)) + } +} + +#[cfg(test)] +mod tests { + use crate::{account::AccountHash, system_contract_errors::pos::Error, U512}; + + use super::Stakes; + + const KEY1: [u8; 32] = [1; 32]; + const KEY2: [u8; 32] = [2; 32]; + + fn new_stakes(stakes: &[([u8; 32], u64)]) -> Stakes { + Stakes( + stakes + .iter() + .map(|&(key, amount)| (AccountHash::new(key), U512::from(amount))) + .collect(), + ) + } + + #[test] + fn test_bond() { + let mut stakes = new_stakes(&[(KEY2, 100)]); + assert_eq!( + Ok(()), + stakes.validate_bonding(&AccountHash::new(KEY1), U512::from(5)) + ); + stakes.bond(&AccountHash::new(KEY1), U512::from(5)); + assert_eq!(new_stakes(&[(KEY1, 5), (KEY2, 100)]), stakes); + } + + #[test] + fn test_bond_existing() { + let mut stakes = new_stakes(&[(KEY1, 50), (KEY2, 100)]); + assert_eq!( + Ok(()), + stakes.validate_bonding(&AccountHash::new(KEY1), U512::from(4)) + ); + stakes.bond(&AccountHash::new(KEY1), U512::from(4)); + assert_eq!(new_stakes(&[(KEY1, 54), (KEY2, 100)]), stakes); + } + + #[test] + fn test_bond_too_much_rel() { + let stakes = new_stakes(&[(KEY1, 1_000), (KEY2, 1_000)]); + let total = 1_000 + 1_000; + assert_eq!( + Err(Error::BondTooLarge), + stakes.validate_bonding( + &AccountHash::new(KEY1), + U512::from(super::MAX_REL_INCREASE * total / 1_000_000 + 1), + ), + "Successfully bonded more than the maximum amount." + ); + assert_eq!( + Ok(()), + stakes.validate_bonding( + &AccountHash::new(KEY1), + U512::from(super::MAX_REL_INCREASE * total / 1_000_000), + ), + "Failed to bond the maximum amount." + ); + } + + #[test] + fn test_unbond() { + let mut stakes = new_stakes(&[(KEY1, 5), (KEY2, 100)]); + assert_eq!( + Ok(U512::from(5)), + stakes.unbond(&AccountHash::new(KEY1), None) + ); + assert_eq!(new_stakes(&[(KEY2, 100)]), stakes); + } + + #[test] + fn test_unbond_last_validator() { + let mut stakes = new_stakes(&[(KEY1, 5)]); + assert_eq!( + Err(Error::CannotUnbondLastValidator), + stakes.unbond(&AccountHash::new(KEY1), None) + ); + } + + #[test] + fn test_partially_unbond() { + let mut stakes = new_stakes(&[(KEY1, 50)]); + assert_eq!( + Ok(U512::from(4)), + stakes.unbond(&AccountHash::new(KEY1), Some(U512::from(4))) + ); + assert_eq!(new_stakes(&[(KEY1, 46)]), stakes); + } + + #[test] + fn test_unbond_too_much_rel() { + let mut stakes = new_stakes(&[(KEY1, 999), (KEY2, 1)]); + let total = 999 + 1; + assert_eq!( + Err(Error::UnbondTooLarge), + stakes.unbond( + &AccountHash::new(KEY1), + Some(U512::from(super::MAX_REL_DECREASE * total / 1_000_000 + 1)), + ), + "Successfully unbonded more than the maximum amount." + ); + assert_eq!( + Ok(U512::from(super::MAX_REL_DECREASE * total / 1_000_000)), + stakes.unbond( + &AccountHash::new(KEY1), + Some(U512::from(super::MAX_REL_DECREASE * total / 1_000_000)), + ), + "Failed to unbond the maximum amount." + ); + } +} diff --git a/types/src/proof_of_stake/stakes_provider.rs b/types/src/proof_of_stake/stakes_provider.rs new file mode 100644 index 0000000000..3cbb6bddf9 --- /dev/null +++ b/types/src/proof_of_stake/stakes_provider.rs @@ -0,0 +1,10 @@ +use crate::proof_of_stake::{stakes::Stakes, Result}; + +/// A `StakesProvider` that reads and writes the stakes to/from the contract's known urefs. +pub trait StakesProvider { + /// Read stakes map. + fn read(&self) -> Result; + + /// Write stakes map. + fn write(&mut self, stakes: &Stakes); +} diff --git a/types/src/protocol_version.rs b/types/src/protocol_version.rs new file mode 100644 index 0000000000..3e7d6296ec --- /dev/null +++ b/types/src/protocol_version.rs @@ -0,0 +1,411 @@ +use alloc::vec::Vec; +use core::fmt; + +use crate::{ + bytesrepr::{Error, FromBytes, ToBytes}, + SemVer, +}; + +/// A newtype wrapping a [`SemVer`] which represents a CasperLabs Platform protocol version. +#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct ProtocolVersion(SemVer); + +/// The result of [`ProtocolVersion::check_next_version`]. +#[derive(Debug, PartialEq, Eq)] +pub enum VersionCheckResult { + /// Upgrade possible, installer code is required. + CodeIsRequired, + /// Upgrade possible, installer code is optional. + CodeIsOptional, + /// Upgrade is invalid. + Invalid, +} + +impl VersionCheckResult { + /// Checks if given version result is invalid. + /// + /// Invalid means that a given version can not be followed. + pub fn is_invalid(&self) -> bool { + match self { + VersionCheckResult::Invalid => true, + VersionCheckResult::CodeIsRequired | VersionCheckResult::CodeIsOptional => false, + } + } + + /// Checks if code is required. + /// + /// Any other variant than [`VersionCheckResult::CodeIsRequired`] returns false. + pub fn is_code_required(&self) -> bool { + match self { + VersionCheckResult::CodeIsRequired => true, + _ => false, + } + } +} + +impl ProtocolVersion { + /// Version 1.0.0. + pub const V1_0_0: ProtocolVersion = ProtocolVersion(SemVer { + major: 1, + minor: 0, + patch: 0, + }); + + /// Constructs a new `ProtocolVersion` from `version`. + pub fn new(version: SemVer) -> ProtocolVersion { + ProtocolVersion(version) + } + + /// Constructs a new `ProtocolVersion` from the given semver parts. + pub fn from_parts(major: u32, minor: u32, patch: u32) -> ProtocolVersion { + let sem_ver = SemVer::new(major, minor, patch); + Self::new(sem_ver) + } + + /// Returns the inner [`SemVer`]. + pub fn value(&self) -> SemVer { + self.0 + } + + /// Checks if next version can be followed. + pub fn check_next_version(&self, next: &ProtocolVersion) -> VersionCheckResult { + if next.0.major < self.0.major || next.0.major > self.0.major + 1 { + // Protocol major versions should not go backwards and should increase monotonically by + // 1. + return VersionCheckResult::Invalid; + } + + if next.0.major == self.0.major.saturating_add(1) { + // A major version increase resets both the minor and patch versions to ( 0.0 ). + if next.0.minor != 0 || next.0.patch != 0 { + return VersionCheckResult::Invalid; + } + return VersionCheckResult::CodeIsRequired; + } + + // Covers the equal major versions + debug_assert_eq!(next.0.major, self.0.major); + + if next.0.minor < self.0.minor || next.0.minor > self.0.minor + 1 { + // Protocol minor versions should increase monotonically by 1 within the same major + // version and should not go backwards. + return VersionCheckResult::Invalid; + } + + if next.0.minor == self.0.minor + 1 { + // A minor version increase resets the patch version to ( 0 ). + if next.0.patch != 0 { + return VersionCheckResult::Invalid; + } + return VersionCheckResult::CodeIsOptional; + } + + // Code belows covers equal minor versions + debug_assert_eq!(next.0.minor, self.0.minor); + + // Protocol patch versions should increase monotonically but can be skipped. + if next.0.patch <= self.0.patch { + return VersionCheckResult::Invalid; + } + + VersionCheckResult::CodeIsOptional + } + + /// Checks if given protocol version is compatible with current one. + /// + /// Two protocol versions with different major version are considered to be incompatible. + pub fn is_compatible_with(&self, version: &ProtocolVersion) -> bool { + self.0.major == version.0.major + } +} + +impl ToBytes for ProtocolVersion { + fn to_bytes(&self) -> Result, Error> { + self.value().to_bytes() + } + + fn serialized_length(&self) -> usize { + self.value().serialized_length() + } +} + +impl FromBytes for ProtocolVersion { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (version, rem) = SemVer::from_bytes(bytes)?; + let protocol_version = ProtocolVersion::new(version); + Ok((protocol_version, rem)) + } +} + +impl fmt::Display for ProtocolVersion { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::SemVer; + + #[test] + fn should_follow_version_with_optional_code() { + let value = VersionCheckResult::CodeIsOptional; + assert!(!value.is_invalid()); + assert!(!value.is_code_required()); + } + + #[test] + fn should_follow_version_with_required_code() { + let value = VersionCheckResult::CodeIsRequired; + assert!(!value.is_invalid()); + assert!(value.is_code_required()); + } + + #[test] + fn should_not_follow_version_with_invalid_code() { + let value = VersionCheckResult::Invalid; + assert!(value.is_invalid()); + assert!(!value.is_code_required()); + } + + #[test] + fn should_be_able_to_get_instance() { + let initial_value = SemVer::new(1, 0, 0); + let item = ProtocolVersion::new(initial_value); + assert_eq!(initial_value, item.value(), "should have equal value") + } + + #[test] + fn should_be_able_to_compare_two_instances() { + let lhs = ProtocolVersion::new(SemVer::new(1, 0, 0)); + let rhs = ProtocolVersion::new(SemVer::new(1, 0, 0)); + assert_eq!(lhs, rhs, "should be equal"); + let rhs = ProtocolVersion::new(SemVer::new(2, 0, 0)); + assert_ne!(lhs, rhs, "should not be equal") + } + + #[test] + fn should_be_able_to_default() { + let defaulted = ProtocolVersion::default(); + let expected = ProtocolVersion::new(SemVer::new(0, 0, 0)); + assert_eq!(defaulted, expected, "should be equal") + } + + #[test] + fn should_be_able_to_compare_relative_value() { + let lhs = ProtocolVersion::new(SemVer::new(2, 0, 0)); + let rhs = ProtocolVersion::new(SemVer::new(1, 0, 0)); + assert!(lhs > rhs, "should be gt"); + let rhs = ProtocolVersion::new(SemVer::new(2, 0, 0)); + assert!(lhs >= rhs, "should be gte"); + assert!(lhs <= rhs, "should be lte"); + let lhs = ProtocolVersion::new(SemVer::new(1, 0, 0)); + assert!(lhs < rhs, "should be lt"); + } + + #[test] + fn should_follow_major_version_upgrade() { + // If the upgrade protocol version is lower than or the same as EE's current in-use protocol + // version the upgrade is rejected and an error is returned; this includes the special case + // of a defaulted protocol version ( 0.0.0 ). + let prev = ProtocolVersion::new(SemVer::new(1, 0, 0)); + let next = ProtocolVersion::new(SemVer::new(2, 0, 0)); + assert_eq!( + prev.check_next_version(&next), + VersionCheckResult::CodeIsRequired + ); + } + + #[test] + fn should_reject_if_major_version_decreases() { + let prev = ProtocolVersion::new(SemVer::new(10, 0, 0)); + let next = ProtocolVersion::new(SemVer::new(9, 0, 0)); + // Major version must not decrease ... + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + } + + #[test] + fn should_check_follows_minor_version_upgrade() { + // [major version] may remain the same in the case of a minor or patch version increase. + + // Minor version must not decrease within the same major version + let prev = ProtocolVersion::new(SemVer::new(1, 1, 0)); + let next = ProtocolVersion::new(SemVer::new(1, 2, 0)); + assert_eq!( + prev.check_next_version(&next), + VersionCheckResult::CodeIsOptional + ); + } + + #[test] + fn should_check_if_minor_bump_resets_patch() { + // A minor version increase resets the patch version to ( 0 ). + let prev = ProtocolVersion::new(SemVer::new(1, 2, 0)); + let next = ProtocolVersion::new(SemVer::new(1, 3, 1)); + // wrong - patch version should be reset for minor version increase + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + + let prev = ProtocolVersion::new(SemVer::new(1, 20, 42)); + let next = ProtocolVersion::new(SemVer::new(1, 30, 43)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + } + + #[test] + fn should_check_if_major_resets_minor_and_patch() { + // A major version increase resets both the minor and patch versions to ( 0.0 ). + let prev = ProtocolVersion::new(SemVer::new(1, 0, 0)); + let next = ProtocolVersion::new(SemVer::new(2, 1, 0)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); // wrong - major increase should reset minor + + let next = ProtocolVersion::new(SemVer::new(2, 0, 1)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); // wrong - major increase should reset patch + + let next = ProtocolVersion::new(SemVer::new(2, 1, 1)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + // wrong - major + // increase + // should reset + // minor and patch + } + + #[test] + fn should_reject_patch_version_rollback() { + // Patch version must not decrease or remain the same within the same major and minor + // version pair, but may skip. + let prev = ProtocolVersion::new(SemVer::new(1, 0, 42)); + let next = ProtocolVersion::new(SemVer::new(1, 0, 41)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + let next = ProtocolVersion::new(SemVer::new(1, 0, 13)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + } + + #[test] + fn should_accept_patch_version_update_with_optional_code() { + let prev = ProtocolVersion::new(SemVer::new(1, 0, 0)); + let next = ProtocolVersion::new(SemVer::new(1, 0, 1)); + assert_eq!( + prev.check_next_version(&next), + VersionCheckResult::CodeIsOptional + ); + + let prev = ProtocolVersion::new(SemVer::new(1, 0, 8)); + let next = ProtocolVersion::new(SemVer::new(1, 0, 42)); + assert_eq!( + prev.check_next_version(&next), + VersionCheckResult::CodeIsOptional + ); + } + + #[test] + fn should_accept_minor_version_update_with_optional_code() { + // installer is optional for minor bump + let prev = ProtocolVersion::new(SemVer::new(1, 0, 0)); + let next = ProtocolVersion::new(SemVer::new(1, 1, 0)); + assert_eq!( + prev.check_next_version(&next), + VersionCheckResult::CodeIsOptional + ); + + let prev = ProtocolVersion::new(SemVer::new(3, 98, 0)); + let next = ProtocolVersion::new(SemVer::new(3, 99, 0)); + assert_eq!( + prev.check_next_version(&next), + VersionCheckResult::CodeIsOptional + ); + } + + #[test] + fn should_not_skip_minor_version_within_major_version() { + // minor can be updated only by 1 + let prev = ProtocolVersion::new(SemVer::new(1, 1, 0)); + + let next = ProtocolVersion::new(SemVer::new(1, 3, 0)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + + let next = ProtocolVersion::new(SemVer::new(1, 7, 0)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + } + + #[test] + fn should_reset_minor_and_patch_on_major_bump() { + // no upgrade - minor resets patch + let prev = ProtocolVersion::new(SemVer::new(1, 0, 0)); + let next = ProtocolVersion::new(SemVer::new(2, 1, 1)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + + let prev = ProtocolVersion::new(SemVer::new(1, 1, 1)); + let next = ProtocolVersion::new(SemVer::new(2, 2, 3)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + } + + #[test] + fn should_allow_code_on_major_update() { + // major upgrade requires installer to be present + let prev = ProtocolVersion::new(SemVer::new(1, 0, 0)); + let next = ProtocolVersion::new(SemVer::new(2, 0, 0)); + assert_eq!( + prev.check_next_version(&next), + VersionCheckResult::CodeIsRequired + ); + + let prev = ProtocolVersion::new(SemVer::new(2, 99, 99)); + let next = ProtocolVersion::new(SemVer::new(3, 0, 0)); + assert_eq!( + prev.check_next_version(&next), + VersionCheckResult::CodeIsRequired + ); + } + + #[test] + fn should_not_skip_major_version() { + // can bump only by 1 + let prev = ProtocolVersion::new(SemVer::new(1, 0, 0)); + let next = ProtocolVersion::new(SemVer::new(3, 0, 0)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + } + + #[test] + fn should_reject_major_version_rollback() { + // can bump forward + let prev = ProtocolVersion::new(SemVer::new(2, 0, 0)); + let next = ProtocolVersion::new(SemVer::new(0, 0, 0)); + assert_eq!(prev.check_next_version(&next), VersionCheckResult::Invalid); + } + + #[test] + fn should_check_same_version_is_invalid() { + for ver in &[ + ProtocolVersion::from_parts(1, 0, 0), + ProtocolVersion::from_parts(1, 2, 0), + ProtocolVersion::from_parts(1, 2, 3), + ] { + assert_eq!(ver.check_next_version(&ver), VersionCheckResult::Invalid); + } + } + + #[test] + fn should_not_be_compatible_with_different_major_version() { + let current = ProtocolVersion::from_parts(1, 2, 3); + let other = ProtocolVersion::from_parts(2, 5, 6); + assert!(!current.is_compatible_with(&other)); + + let current = ProtocolVersion::from_parts(1, 0, 0); + let other = ProtocolVersion::from_parts(2, 0, 0); + assert!(!current.is_compatible_with(&other)); + } + + #[test] + fn should_be_compatible_with_equal_major_version_backwards() { + let current = ProtocolVersion::from_parts(1, 99, 99); + let other = ProtocolVersion::from_parts(1, 0, 0); + assert!(current.is_compatible_with(&other)); + } + + #[test] + fn should_be_compatible_with_equal_major_version_forwards() { + let current = ProtocolVersion::from_parts(1, 0, 0); + let other = ProtocolVersion::from_parts(1, 99, 99); + assert!(current.is_compatible_with(&other)); + } +} diff --git a/types/src/runtime_args.rs b/types/src/runtime_args.rs new file mode 100644 index 0000000000..aaf16938cd --- /dev/null +++ b/types/src/runtime_args.rs @@ -0,0 +1,237 @@ +//! Home of RuntimeArgs for calling contracts + +use alloc::{collections::BTreeMap, string::String, vec::Vec}; + +use crate::{ + bytesrepr::{self, Error, FromBytes, ToBytes}, + CLTyped, CLValue, +}; + +/// Named arguments to a contract +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct NamedArg(String, CLValue); + +impl NamedArg { + /// ctor + pub fn new(name: String, value: CLValue) -> Self { + NamedArg(name, value) + } + /// returns `name` + pub fn name(&self) -> &str { + &self.0 + } + /// returns `value` + pub fn cl_value(&self) -> &CLValue { + &self.1 + } +} + +impl From<(String, CLValue)> for NamedArg { + fn from((name, value): (String, CLValue)) -> NamedArg { + NamedArg(name, value) + } +} + +impl ToBytes for NamedArg { + fn to_bytes(&self) -> Result, Error> { + let mut result = bytesrepr::allocate_buffer(self)?; + result.append(&mut self.0.to_bytes()?); + result.append(&mut self.1.to_bytes()?); + Ok(result) + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + self.1.serialized_length() + } +} + +impl FromBytes for NamedArg { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (name, remainder) = String::from_bytes(bytes)?; + let (cl_value, remainder) = CLValue::from_bytes(remainder)?; + Ok((NamedArg(name, cl_value), remainder)) + } +} + +/// Represents a collection of arguments passed to a smart contract. +#[derive(PartialEq, Eq, Clone, Debug, Default)] +pub struct RuntimeArgs(Vec); + +impl RuntimeArgs { + /// Create an empty [`RuntimeArgs`] instance. + pub fn new() -> RuntimeArgs { + RuntimeArgs::default() + } + + /// Gets an argument by its name. + pub fn get(&self, name: &str) -> Option<&CLValue> { + self.0.iter().find_map(|NamedArg(named_name, named_value)| { + if named_name == name { + Some(named_value) + } else { + None + } + }) + } + + /// Get length of the collection. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Check if collection of arguments is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Insert new named argument into the collection. + pub fn insert(&mut self, key: K, value: V) + where + K: Into, + V: CLTyped + ToBytes, + { + let cl_value = CLValue::from_t(value).expect("should create CLValue"); + self.0.push(NamedArg(key.into(), cl_value)); + } + + /// Insert new named argument into the collection. + pub fn insert_cl_value(&mut self, key: K, cl_value: CLValue) + where + K: Into, + { + self.0.push(NamedArg(key.into(), cl_value)); + } + + /// Returns values held regardless of the variant. + pub fn to_values(&self) -> Vec<&CLValue> { + self.0.iter().map(|NamedArg(_name, value)| value).collect() + } +} + +impl From> for RuntimeArgs { + fn from(values: Vec) -> Self { + RuntimeArgs(values) + } +} + +impl From> for RuntimeArgs { + fn from(cl_values: BTreeMap) -> RuntimeArgs { + RuntimeArgs(cl_values.into_iter().map(NamedArg::from).collect()) + } +} + +impl Into> for RuntimeArgs { + fn into(self) -> BTreeMap { + let mut map = BTreeMap::new(); + for named in self.0 { + map.insert(named.0, named.1); + } + map + } +} + +impl ToBytes for RuntimeArgs { + fn to_bytes(&self) -> Result, Error> { + self.0.to_bytes() + } + + fn serialized_length(&self) -> usize { + self.0.serialized_length() + } +} + +impl FromBytes for RuntimeArgs { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (args, remainder) = Vec::::from_bytes(bytes)?; + Ok((RuntimeArgs(args), remainder)) + } +} + +/// Macro that makes it easier to construct named arguments. +/// +/// # Example usage +/// ``` +/// use casperlabs_types::{RuntimeArgs, runtime_args}; +/// let _named_args = runtime_args! { +/// "foo" => 42, +/// "bar" => "Hello, world!" +/// }; +/// ``` +#[macro_export] +macro_rules! runtime_args { + () => (RuntimeArgs::new()); + ( $($key:expr => $value:expr,)+ ) => (runtime_args!($($key => $value),+)); + ( $($key:expr => $value:expr),* ) => { + { + let mut named_args = RuntimeArgs::new(); + $( + named_args.insert($key, $value); + )* + named_args + } + }; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_runtime_args() { + let arg1 = CLValue::from_t(1).unwrap(); + let arg2 = CLValue::from_t("Foo").unwrap(); + let arg3 = CLValue::from_t(Some(1)).unwrap(); + let args = { + let mut map = BTreeMap::new(); + map.insert("bar".into(), arg2.clone()); + map.insert("foo".into(), arg1.clone()); + map.insert("qwer".into(), arg3.clone()); + map + }; + let runtime_args = RuntimeArgs::from(args); + assert_eq!(runtime_args.get("qwer"), Some(&arg3)); + assert_eq!(runtime_args.get("foo"), Some(&arg1)); + assert_eq!(runtime_args.get("bar"), Some(&arg2)); + assert_eq!(runtime_args.get("aaa"), None); + + // Ensure macro works + + let runtime_args_2 = runtime_args! { + "bar" => "Foo", + "foo" => 1i32, + "qwer" => Some(1i32), + }; + assert_eq!(runtime_args, runtime_args_2); + } + + #[test] + fn empty_macro() { + assert_eq!(runtime_args! {}, RuntimeArgs::new()); + } + + #[test] + fn btreemap_compat() { + // This test assumes same serialization format as BTreeMap + let runtime_args_1 = runtime_args! { + "bar" => "Foo", + "foo" => 1i32, + "qwer" => Some(1i32), + }; + let tagless = runtime_args_1.to_bytes().unwrap().to_vec(); + + let mut runtime_args_2 = BTreeMap::new(); + runtime_args_2.insert(String::from("bar"), CLValue::from_t("Foo").unwrap()); + runtime_args_2.insert(String::from("foo"), CLValue::from_t(1i32).unwrap()); + runtime_args_2.insert(String::from("qwer"), CLValue::from_t(Some(1i32)).unwrap()); + + assert_eq!(tagless, runtime_args_2.to_bytes().unwrap()); + } + + #[test] + fn named_serialization_roundtrip() { + let args = runtime_args! { + "foo" => 1i32, + }; + bytesrepr::test_serialization_roundtrip(&args); + } +} diff --git a/types/src/semver.rs b/types/src/semver.rs new file mode 100644 index 0000000000..106d0a8c8f --- /dev/null +++ b/types/src/semver.rs @@ -0,0 +1,133 @@ +use alloc::vec::Vec; +use core::{convert::TryFrom, fmt, num::ParseIntError}; + +use failure::Fail; + +use crate::bytesrepr::{self, Error, FromBytes, ToBytes, U32_SERIALIZED_LENGTH}; + +/// Length of SemVer when serialized +pub const SEM_VER_SERIALIZED_LENGTH: usize = 3 * U32_SERIALIZED_LENGTH; + +/// A struct for semantic versioning. +#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct SemVer { + /// Major version. + pub major: u32, + /// Minor version. + pub minor: u32, + /// Patch version. + pub patch: u32, +} + +impl SemVer { + /// Version 1.0.0. + pub const V1_0_0: SemVer = SemVer { + major: 1, + minor: 0, + patch: 0, + }; + + /// Constructs a new `SemVer` from the given semver parts. + pub const fn new(major: u32, minor: u32, patch: u32) -> SemVer { + SemVer { + major, + minor, + patch, + } + } +} + +impl ToBytes for SemVer { + fn to_bytes(&self) -> Result, Error> { + let mut ret = bytesrepr::unchecked_allocate_buffer(self); + ret.append(&mut self.major.to_bytes()?); + ret.append(&mut self.minor.to_bytes()?); + ret.append(&mut self.patch.to_bytes()?); + Ok(ret) + } + + fn serialized_length(&self) -> usize { + SEM_VER_SERIALIZED_LENGTH + } +} + +impl FromBytes for SemVer { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (major, rem): (u32, &[u8]) = FromBytes::from_bytes(bytes)?; + let (minor, rem): (u32, &[u8]) = FromBytes::from_bytes(rem)?; + let (patch, rem): (u32, &[u8]) = FromBytes::from_bytes(rem)?; + Ok((SemVer::new(major, minor, patch), rem)) + } +} + +impl fmt::Display for SemVer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } +} + +#[derive(Fail, Debug, Clone, PartialEq, Eq)] +pub enum ParseSemVerError { + #[fail(display = "Invalid version format")] + InvalidVersionFormat, + #[fail(display = "{}", _0)] + ParseIntError(ParseIntError), +} + +impl From for ParseSemVerError { + fn from(error: ParseIntError) -> ParseSemVerError { + ParseSemVerError::ParseIntError(error) + } +} + +impl TryFrom<&str> for SemVer { + type Error = ParseSemVerError; + fn try_from(value: &str) -> Result { + let tokens: Vec<&str> = value.split('.').collect(); + if tokens.len() != 3 { + return Err(ParseSemVerError::InvalidVersionFormat); + } + + Ok(SemVer { + major: tokens[0].parse()?, + minor: tokens[1].parse()?, + patch: tokens[2].parse()?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::convert::TryInto; + + #[test] + fn should_compare_semver_versions() { + assert!(SemVer::new(0, 0, 0) < SemVer::new(1, 2, 3)); + assert!(SemVer::new(1, 1, 0) < SemVer::new(1, 2, 0)); + assert!(SemVer::new(1, 0, 0) < SemVer::new(1, 2, 0)); + assert!(SemVer::new(1, 0, 0) < SemVer::new(1, 2, 3)); + assert!(SemVer::new(1, 2, 0) < SemVer::new(1, 2, 3)); + assert!(SemVer::new(1, 2, 3) == SemVer::new(1, 2, 3)); + assert!(SemVer::new(1, 2, 3) >= SemVer::new(1, 2, 3)); + assert!(SemVer::new(1, 2, 3) <= SemVer::new(1, 2, 3)); + assert!(SemVer::new(2, 0, 0) >= SemVer::new(1, 99, 99)); + assert!(SemVer::new(2, 0, 0) > SemVer::new(1, 99, 99)); + } + + #[test] + fn parse_from_string() { + let ver1: SemVer = "100.20.3".try_into().expect("should parse"); + assert_eq!(ver1, SemVer::new(100, 20, 3)); + let ver2: SemVer = "0.0.1".try_into().expect("should parse"); + assert_eq!(ver2, SemVer::new(0, 0, 1)); + + assert!(SemVer::try_from("1.a.2.3").is_err()); + assert!(SemVer::try_from("1. 2.3").is_err()); + assert!(SemVer::try_from("12345124361461.0.1").is_err()); + assert!(SemVer::try_from("1.2.3.4").is_err()); + assert!(SemVer::try_from("1.2").is_err()); + assert!(SemVer::try_from("1").is_err()); + assert!(SemVer::try_from("0").is_err()); + } +} diff --git a/types/src/standard_payment.rs b/types/src/standard_payment.rs new file mode 100644 index 0000000000..5a29275f15 --- /dev/null +++ b/types/src/standard_payment.rs @@ -0,0 +1,24 @@ +//! Contains implementation of a standard payment contract implementation. +mod account_provider; +mod mint_provider; +mod proof_of_stake_provider; + +use core::marker::Sized; + +use crate::{ApiError, U512}; + +pub use crate::standard_payment::{ + account_provider::AccountProvider, mint_provider::MintProvider, + proof_of_stake_provider::ProofOfStakeProvider, +}; + +/// Implementation of a standard payment contract. +pub trait StandardPayment: AccountProvider + MintProvider + ProofOfStakeProvider + Sized { + /// Pay `amount` to a payment purse. + fn pay(&mut self, amount: U512) -> Result<(), ApiError> { + let main_purse = self.get_main_purse()?; + let payment_purse = self.get_payment_purse()?; + self.transfer_purse_to_purse(main_purse, payment_purse, amount) + .map_err(|_| ApiError::Transfer) + } +} diff --git a/types/src/standard_payment/account_provider.rs b/types/src/standard_payment/account_provider.rs new file mode 100644 index 0000000000..edab1cc690 --- /dev/null +++ b/types/src/standard_payment/account_provider.rs @@ -0,0 +1,7 @@ +use crate::{ApiError, URef}; + +/// Provider of an account related functionality. +pub trait AccountProvider { + /// Get currently executing account's purse. + fn get_main_purse(&self) -> Result; +} diff --git a/types/src/standard_payment/mint_provider.rs b/types/src/standard_payment/mint_provider.rs new file mode 100644 index 0000000000..92c1b4a785 --- /dev/null +++ b/types/src/standard_payment/mint_provider.rs @@ -0,0 +1,12 @@ +use crate::{ApiError, URef, U512}; + +/// Provides an access to mint. +pub trait MintProvider { + /// Transfer `amount` of tokens from `source` purse to a `target` purse. + fn transfer_purse_to_purse( + &mut self, + source: URef, + target: URef, + amount: U512, + ) -> Result<(), ApiError>; +} diff --git a/types/src/standard_payment/proof_of_stake_provider.rs b/types/src/standard_payment/proof_of_stake_provider.rs new file mode 100644 index 0000000000..2c044eae64 --- /dev/null +++ b/types/src/standard_payment/proof_of_stake_provider.rs @@ -0,0 +1,7 @@ +use crate::{ApiError, URef}; + +/// Provider of proof of stake functionality. +pub trait ProofOfStakeProvider { + /// Get payment purse for given deploy. + fn get_payment_purse(&mut self) -> Result; +} diff --git a/types/src/system_contract_errors/mint.rs b/types/src/system_contract_errors/mint.rs new file mode 100644 index 0000000000..a1ce22ff52 --- /dev/null +++ b/types/src/system_contract_errors/mint.rs @@ -0,0 +1,131 @@ +//! Home of the Mint contract's [`Error`] type. + +use alloc::{fmt, vec::Vec}; +use core::convert::{TryFrom, TryInto}; + +use failure::Fail; + +use crate::{ + bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, + AccessRights, CLType, CLTyped, +}; + +/// Errors which can occur while executing the Mint contract. +#[derive(Fail, Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum Error { + /// Insufficient funds to complete the transfer. + #[fail(display = "Insufficient funds")] + InsufficientFunds = 0, + /// Source purse not found. + #[fail(display = "Source not found")] + SourceNotFound = 1, + /// Destination purse not found. + #[fail(display = "Destination not found")] + DestNotFound = 2, + /// See [`PurseError::InvalidURef`]. + #[fail(display = "Invalid URef")] + InvalidURef = 3, + /// See [`PurseError::InvalidAccessRights`]. + #[fail(display = "Invalid AccessRights")] + InvalidAccessRights = 4, + /// Tried to create a new purse with a non-zero initial balance. + #[fail(display = "Invalid non-empty purse creation")] + InvalidNonEmptyPurseCreation = 5, + /// Failed to read from local or global storage. + #[fail(display = "Storage error")] + Storage = 6, + /// Purse not found while trying to get balance. + #[fail(display = "Purse not found")] + PurseNotFound = 7, +} + +impl From for Error { + fn from(purse_error: PurseError) -> Error { + match purse_error { + PurseError::InvalidURef => Error::InvalidURef, + PurseError::InvalidAccessRights(_) => { + // This one does not carry state from PurseError to the new Error enum. The reason + // is that Error is supposed to be simple in serialization and deserialization, so + // extra state is currently discarded. + Error::InvalidAccessRights + } + } + } +} + +impl CLTyped for Error { + fn cl_type() -> CLType { + CLType::U8 + } +} + +// This error type is not intended to be used by third party crates. +#[doc(hidden)] +pub struct TryFromU8ForError(()); + +// This conversion is not intended to be used by third party crates. +#[doc(hidden)] +impl TryFrom for Error { + type Error = TryFromU8ForError; + + fn try_from(value: u8) -> Result { + match value { + d if d == Error::InsufficientFunds as u8 => Ok(Error::InsufficientFunds), + d if d == Error::SourceNotFound as u8 => Ok(Error::SourceNotFound), + d if d == Error::DestNotFound as u8 => Ok(Error::DestNotFound), + d if d == Error::InvalidURef as u8 => Ok(Error::InvalidURef), + d if d == Error::InvalidAccessRights as u8 => Ok(Error::InvalidAccessRights), + d if d == Error::InvalidNonEmptyPurseCreation as u8 => { + Ok(Error::InvalidNonEmptyPurseCreation) + } + _ => Err(TryFromU8ForError(())), + } + } +} + +impl ToBytes for Error { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let value = *self as u8; + value.to_bytes() + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + } +} + +impl FromBytes for Error { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (value, rem): (u8, _) = FromBytes::from_bytes(bytes)?; + let error: Error = value + .try_into() + // In case an Error variant is unable to be determined it would return an + // Error::Formatting as if its unable to be correctly deserialized. + .map_err(|_| bytesrepr::Error::Formatting)?; + Ok((error, rem)) + } +} + +/// Errors relating to validity of source or destination purses. +#[derive(Debug, Copy, Clone)] +pub enum PurseError { + /// The given [`URef`](crate::URef) does not reference the account holder's purse, or such a + /// [`URef`](crate::URef) does not have the required [`AccessRights`]. + InvalidURef, + /// The source purse is not writeable (see [`URef::is_writeable`](crate::URef::is_writeable)), + /// or the destination purse is not addable (see + /// [`URef::is_addable`](crate::URef::is_addable)). + InvalidAccessRights(Option), +} + +impl fmt::Display for PurseError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + PurseError::InvalidURef => write!(f, "invalid uref"), + PurseError::InvalidAccessRights(maybe_access_rights) => { + write!(f, "invalid access rights: {:?}", maybe_access_rights) + } + } + } +} diff --git a/types/src/system_contract_errors/mod.rs b/types/src/system_contract_errors/mod.rs new file mode 100644 index 0000000000..a1c9a12ba5 --- /dev/null +++ b/types/src/system_contract_errors/mod.rs @@ -0,0 +1,28 @@ +//! Home of error types returned by system contracts. +use failure::Fail; + +pub mod mint; +pub mod pos; + +/// An aggregate enum error with variants for each system contract's error. +#[derive(Fail, Debug, Copy, Clone)] +pub enum Error { + /// Contains a [`mint::Error`]. + #[fail(display = "Mint error: {}", _0)] + Mint(mint::Error), + /// Contains a [`pos::Error`]. + #[fail(display = "Proof of Stake error: {}", _0)] + Pos(pos::Error), +} + +impl From for Error { + fn from(error: mint::Error) -> Error { + Error::Mint(error) + } +} + +impl From for Error { + fn from(error: pos::Error) -> Error { + Error::Pos(error) + } +} diff --git a/types/src/system_contract_errors/pos.rs b/types/src/system_contract_errors/pos.rs new file mode 100644 index 0000000000..50a0c753d6 --- /dev/null +++ b/types/src/system_contract_errors/pos.rs @@ -0,0 +1,160 @@ +//! Home of the Proof of Stake contract's [`Error`] type. +use failure::Fail; + +use alloc::vec::Vec; +use core::result; + +use crate::{ + bytesrepr::{self, ToBytes, U8_SERIALIZED_LENGTH}, + CLType, CLTyped, +}; + +/// Errors which can occur while executing the Proof of Stake contract. +// TODO: Split this up into user errors vs. system errors. +#[derive(Fail, Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum Error { + // ===== User errors ===== + /// The given validator is not bonded. + #[fail(display = "Not bonded")] + NotBonded = 0, + /// There are too many bonding or unbonding attempts already enqueued to allow more. + #[fail(display = "Too many events in queue")] + TooManyEventsInQueue, + /// At least one validator must remain bonded. + #[fail(display = "Cannot unbond last validator")] + CannotUnbondLastValidator, + /// Failed to bond or unbond as this would have resulted in exceeding the maximum allowed + /// difference between the largest and smallest stakes. + #[fail(display = "Spread is too high")] + SpreadTooHigh, + /// The given validator already has a bond or unbond attempt enqueued. + #[fail(display = "Multiple requests")] + MultipleRequests, + /// Attempted to bond with a stake which was too small. + #[fail(display = "Bond is too small")] + BondTooSmall, + /// Attempted to bond with a stake which was too large. + #[fail(display = "Bond is too large")] + BondTooLarge, + /// Attempted to unbond an amount which was too large. + #[fail(display = "Unbond is too large")] + UnbondTooLarge, + /// While bonding, the transfer from source purse to the Proof of Stake internal purse failed. + #[fail(display = "Bond transfer failed")] + BondTransferFailed, + /// While unbonding, the transfer from the Proof of Stake internal purse to the destination + /// purse failed. + #[fail(display = "Unbond transfer failed")] + UnbondTransferFailed, + // ===== System errors ===== + /// Internal error: a [`BlockTime`](crate::BlockTime) was unexpectedly out of sequence. + #[fail(display = "Time went backwards")] + TimeWentBackwards, + /// Internal error: stakes were unexpectedly empty. + #[fail(display = "Stakes not found")] + StakesNotFound, + /// Internal error: the PoS contract's payment purse wasn't found. + #[fail(display = "Payment purse not found")] + PaymentPurseNotFound, + /// Internal error: the PoS contract's payment purse key was the wrong type. + #[fail(display = "Payment purse has unexpected type")] + PaymentPurseKeyUnexpectedType, + /// Internal error: couldn't retrieve the balance for the PoS contract's payment purse. + #[fail(display = "Payment purse balance not found")] + PaymentPurseBalanceNotFound, + /// Internal error: the PoS contract's bonding purse wasn't found. + #[fail(display = "Bonding purse not found")] + BondingPurseNotFound, + /// Internal error: the PoS contract's bonding purse key was the wrong type. + #[fail(display = "Bonding purse key has unexpected type")] + BondingPurseKeyUnexpectedType, + /// Internal error: the PoS contract's refund purse key was the wrong type. + #[fail(display = "Refund purse key has unexpected type")] + RefundPurseKeyUnexpectedType, + /// Internal error: the PoS contract's rewards purse wasn't found. + #[fail(display = "Rewards purse not found")] + RewardsPurseNotFound, + /// Internal error: the PoS contract's rewards purse key was the wrong type. + #[fail(display = "Rewards purse has unexpected type")] + RewardsPurseKeyUnexpectedType, + // TODO: Put these in their own enum, and wrap them separately in `BondingError` and + // `UnbondingError`. + /// Internal error: failed to deserialize the stake's key. + #[fail(display = "Failed to deserialize stake's key")] + StakesKeyDeserializationFailed, + /// Internal error: failed to deserialize the stake's balance. + #[fail(display = "Failed to deserialize stake's balance")] + StakesDeserializationFailed, + /// The invoked PoS function can only be called by system contracts, but was called by a user + /// contract. + #[fail(display = "System function was called by user account")] + SystemFunctionCalledByUserAccount, + /// Internal error: while finalizing payment, the amount spent exceeded the amount available. + #[fail(display = "Insufficient payment for amount spent")] + InsufficientPaymentForAmountSpent, + /// Internal error: while finalizing payment, failed to pay the validators (the transfer from + /// the PoS contract's payment purse to rewards purse failed). + #[fail(display = "Transfer to rewards purse has failed")] + FailedTransferToRewardsPurse, + /// Internal error: while finalizing payment, failed to refund the caller's purse (the transfer + /// from the PoS contract's payment purse to refund purse or account's main purse failed). + #[fail(display = "Transfer to account's purse failed")] + FailedTransferToAccountPurse, + /// PoS contract's "set_refund_purse" method can only be called by the payment code of a + /// deploy, but was called by the session code. + #[fail(display = "Set refund purse was called outside payment")] + SetRefundPurseCalledOutsidePayment, +} + +impl CLTyped for Error { + fn cl_type() -> CLType { + CLType::U8 + } +} + +impl ToBytes for Error { + fn to_bytes(&self) -> result::Result, bytesrepr::Error> { + let value = *self as u8; + value.to_bytes() + } + + fn serialized_length(&self) -> usize { + U8_SERIALIZED_LENGTH + } +} + +/// An alias for `Result`. +pub type Result = result::Result; + +// This error type is not intended to be used by third party crates. +#[doc(hidden)] +pub enum PurseLookupError { + KeyNotFound, + KeyUnexpectedType, +} + +// This error type is not intended to be used by third party crates. +#[doc(hidden)] +impl PurseLookupError { + pub fn bonding(err: PurseLookupError) -> Error { + match err { + PurseLookupError::KeyNotFound => Error::BondingPurseNotFound, + PurseLookupError::KeyUnexpectedType => Error::BondingPurseKeyUnexpectedType, + } + } + + pub fn payment(err: PurseLookupError) -> Error { + match err { + PurseLookupError::KeyNotFound => Error::PaymentPurseNotFound, + PurseLookupError::KeyUnexpectedType => Error::PaymentPurseKeyUnexpectedType, + } + } + + pub fn rewards(err: PurseLookupError) -> Error { + match err { + PurseLookupError::KeyNotFound => Error::RewardsPurseNotFound, + PurseLookupError::KeyUnexpectedType => Error::RewardsPurseKeyUnexpectedType, + } + } +} diff --git a/types/src/system_contract_type.rs b/types/src/system_contract_type.rs new file mode 100644 index 0000000000..38be344cd8 --- /dev/null +++ b/types/src/system_contract_type.rs @@ -0,0 +1,120 @@ +//! Home of system contract type enum. + +use core::{ + convert::TryFrom, + fmt::{self, Display, Formatter}, +}; + +use crate::ApiError; + +/// System contract types. +/// +/// Used by converting to a `u32` and passing as the `system_contract_index` argument of +/// `ext_ffi::get_system_contract()`. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum SystemContractType { + /// Mint contract. + Mint, + /// Proof of Stake contract. + ProofOfStake, + /// Standard Payment contract. + StandardPayment, +} + +/// Name of mint system contract +const MINT: &str = "mint"; +/// Name of proof of stake system contract +pub const PROOF_OF_STAKE: &str = "proof of stake"; +/// Name of standard payment system contract +const STANDARD_PAYMENT: &str = "standard payment"; + +impl From for u32 { + fn from(system_contract_type: SystemContractType) -> u32 { + match system_contract_type { + SystemContractType::Mint => 0, + SystemContractType::ProofOfStake => 1, + SystemContractType::StandardPayment => 2, + } + } +} + +// This conversion is not intended to be used by third party crates. +#[doc(hidden)] +impl TryFrom for SystemContractType { + type Error = ApiError; + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(SystemContractType::Mint), + 1 => Ok(SystemContractType::ProofOfStake), + 2 => Ok(SystemContractType::StandardPayment), + _ => Err(ApiError::InvalidSystemContract), + } + } +} + +impl Display for SystemContractType { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match *self { + SystemContractType::Mint => write!(f, "{}", MINT), + SystemContractType::ProofOfStake => write!(f, "{}", PROOF_OF_STAKE), + SystemContractType::StandardPayment => write!(f, "{}", STANDARD_PAYMENT), + } + } +} + +#[cfg(test)] +mod tests { + use std::string::ToString; + + use super::*; + + #[test] + fn get_index_of_mint_contract() { + let index: u32 = SystemContractType::Mint.into(); + assert_eq!(index, 0u32); + assert_eq!(SystemContractType::Mint.to_string(), MINT); + } + + #[test] + fn get_index_of_pos_contract() { + let index: u32 = SystemContractType::ProofOfStake.into(); + assert_eq!(index, 1u32); + assert_eq!(SystemContractType::ProofOfStake.to_string(), PROOF_OF_STAKE); + } + + #[test] + fn get_index_of_standard_payment_contract() { + let index: u32 = SystemContractType::StandardPayment.into(); + assert_eq!(index, 2u32); + assert_eq!( + SystemContractType::StandardPayment.to_string(), + STANDARD_PAYMENT + ); + } + + #[test] + fn create_mint_variant_from_int() { + let mint = SystemContractType::try_from(0).ok().unwrap(); + assert_eq!(mint, SystemContractType::Mint); + } + + #[test] + fn create_pos_variant_from_int() { + let pos = SystemContractType::try_from(1).ok().unwrap(); + assert_eq!(pos, SystemContractType::ProofOfStake); + } + + #[test] + fn create_standard_payment_variant_from_int() { + let pos = SystemContractType::try_from(2).ok().unwrap(); + assert_eq!(pos, SystemContractType::StandardPayment); + } + + #[test] + fn create_unknown_system_contract_variant() { + assert!(SystemContractType::try_from(3).is_err()); + assert!(SystemContractType::try_from(4).is_err()); + assert!(SystemContractType::try_from(10).is_err()); + assert!(SystemContractType::try_from(u32::max_value()).is_err()); + } +} diff --git a/types/src/transfer_result.rs b/types/src/transfer_result.rs new file mode 100644 index 0000000000..ba9ce66bc4 --- /dev/null +++ b/types/src/transfer_result.rs @@ -0,0 +1,39 @@ +use core::fmt::Debug; + +use crate::ApiError; + +/// The result of an attempt to transfer between purses. +pub type TransferResult = Result; + +/// The result of a successful transfer between purses. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(i32)] +pub enum TransferredTo { + /// The destination account already existed. + ExistingAccount = 0, + /// The destination account was created. + NewAccount = 1, +} + +impl TransferredTo { + /// Converts an `i32` to a [`TransferResult`], where: + /// * `0` represents `Ok(TransferredTo::ExistingAccount)`, + /// * `1` represents `Ok(TransferredTo::NewAccount)`, + /// * all other inputs are mapped to `Err(ApiError::Transfer)`. + pub fn result_from(value: i32) -> TransferResult { + match value { + x if x == TransferredTo::ExistingAccount as i32 => Ok(TransferredTo::ExistingAccount), + x if x == TransferredTo::NewAccount as i32 => Ok(TransferredTo::NewAccount), + _ => Err(ApiError::Transfer), + } + } + + // This conversion is not intended to be used by third party crates. + #[doc(hidden)] + pub fn i32_from(result: TransferResult) -> i32 { + match result { + Ok(transferred_to) => transferred_to as i32, + Err(_) => 2, + } + } +} diff --git a/types/src/uint.rs b/types/src/uint.rs new file mode 100644 index 0000000000..77708af26b --- /dev/null +++ b/types/src/uint.rs @@ -0,0 +1,789 @@ +use alloc::vec::Vec; + +use num_integer::Integer; +use num_traits::{AsPrimitive, Bounded, Num, One, Unsigned, WrappingAdd, WrappingSub, Zero}; + +use crate::bytesrepr::{self, Error, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}; + +#[allow( + clippy::assign_op_pattern, + clippy::ptr_offset_with_cast, + clippy::range_plus_one, + clippy::transmute_ptr_to_ptr, + clippy::reversed_empty_ranges +)] +mod macro_code { + use uint::construct_uint; + + construct_uint! { + pub struct U512(8); + } + construct_uint! { + pub struct U256(4); + } + construct_uint! { + pub struct U128(2); + } +} + +pub use self::macro_code::{U128, U256, U512}; + +/// Error type for parsing [`U128`], [`U256`], [`U512`] from a string. +#[derive(Debug)] +pub enum UIntParseError { + /// Contains the parsing error from the `uint` crate, which only supports base-10 parsing. + FromDecStr(uint::FromDecStrErr), + /// Parsing was attempted on a string representing the number in some base other than 10. + /// + /// Note: a general radix may be supported in the future. + InvalidRadix, +} + +macro_rules! impl_traits_for_uint { + ($type:ident, $total_bytes:expr, $test_mod:ident) => { + impl ToBytes for $type { + fn to_bytes(&self) -> Result, Error> { + let mut buf = [0u8; $total_bytes]; + self.to_little_endian(&mut buf); + let mut non_zero_bytes: Vec = + buf.iter().rev().skip_while(|b| **b == 0).cloned().collect(); + let num_bytes = non_zero_bytes.len() as u8; + non_zero_bytes.push(num_bytes); + non_zero_bytes.reverse(); + Ok(non_zero_bytes) + } + + fn serialized_length(&self) -> usize { + let mut buf = [0u8; $total_bytes]; + self.to_little_endian(&mut buf); + let non_zero_bytes = buf.iter().rev().skip_while(|b| **b == 0).count(); + U8_SERIALIZED_LENGTH + non_zero_bytes + } + } + + impl FromBytes for $type { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (num_bytes, rem): (u8, &[u8]) = FromBytes::from_bytes(bytes)?; + + if num_bytes > $total_bytes { + Err(Error::Formatting) + } else { + let (value, rem) = bytesrepr::safe_split_at(rem, num_bytes as usize)?; + let result = $type::from_little_endian(value); + Ok((result, rem)) + } + } + } + + // Trait implementations for unifying U* as numeric types + impl Zero for $type { + fn zero() -> Self { + $type::zero() + } + + fn is_zero(&self) -> bool { + self.is_zero() + } + } + + impl One for $type { + fn one() -> Self { + $type::one() + } + } + + // Requires Zero and One to be implemented + impl Num for $type { + type FromStrRadixErr = UIntParseError; + fn from_str_radix(str: &str, radix: u32) -> Result { + if radix == 10 { + $type::from_dec_str(str).map_err(UIntParseError::FromDecStr) + } else { + // TODO: other radix parsing + Err(UIntParseError::InvalidRadix) + } + } + } + + // Requires Num to be implemented + impl Unsigned for $type {} + + // Additional numeric trait, which also holds for these types + impl Bounded for $type { + fn min_value() -> Self { + $type::zero() + } + + fn max_value() -> Self { + $type::MAX + } + } + + // Instead of implementing arbitrary methods we can use existing traits from num_trait + // crate. + impl WrappingAdd for $type { + fn wrapping_add(&self, other: &$type) -> $type { + self.overflowing_add(*other).0 + } + } + + impl WrappingSub for $type { + fn wrapping_sub(&self, other: &$type) -> $type { + self.overflowing_sub(*other).0 + } + } + + impl Integer for $type { + /// Unsigned integer division. Returns the same result as `div` (`/`). + #[inline] + fn div_floor(&self, other: &Self) -> Self { + *self / *other + } + + /// Unsigned integer modulo operation. Returns the same result as `rem` (`%`). + #[inline] + fn mod_floor(&self, other: &Self) -> Self { + *self % *other + } + + /// Calculates the Greatest Common Divisor (GCD) of the number and `other` + #[inline] + fn gcd(&self, other: &Self) -> Self { + let zero = Self::zero(); + // Use Stein's algorithm + let mut m = *self; + let mut n = *other; + if m == zero || n == zero { + return m | n; + } + + // find common factors of 2 + let shift = (m | n).trailing_zeros(); + + // divide n and m by 2 until odd + m >>= m.trailing_zeros(); + n >>= n.trailing_zeros(); + + while m != n { + if m > n { + m -= n; + m >>= m.trailing_zeros(); + } else { + n -= m; + n >>= n.trailing_zeros(); + } + } + m << shift + } + + /// Calculates the Lowest Common Multiple (LCM) of the number and `other`. + #[inline] + fn lcm(&self, other: &Self) -> Self { + self.gcd_lcm(other).1 + } + + /// Calculates the Greatest Common Divisor (GCD) and + /// Lowest Common Multiple (LCM) of the number and `other`. + #[inline] + fn gcd_lcm(&self, other: &Self) -> (Self, Self) { + if self.is_zero() && other.is_zero() { + return (Self::zero(), Self::zero()); + } + let gcd = self.gcd(other); + let lcm = *self * (*other / gcd); + (gcd, lcm) + } + + /// Deprecated, use `is_multiple_of` instead. + #[inline] + fn divides(&self, other: &Self) -> bool { + self.is_multiple_of(other) + } + + /// Returns `true` if the number is a multiple of `other`. + #[inline] + fn is_multiple_of(&self, other: &Self) -> bool { + *self % *other == $type::zero() + } + + /// Returns `true` if the number is divisible by `2`. + #[inline] + fn is_even(&self) -> bool { + (self.0[0]) & 1 == 0 + } + + /// Returns `true` if the number is not divisible by `2`. + #[inline] + fn is_odd(&self) -> bool { + !self.is_even() + } + + /// Simultaneous truncated integer division and modulus. + #[inline] + fn div_rem(&self, other: &Self) -> (Self, Self) { + (*self / *other, *self % *other) + } + } + + impl AsPrimitive<$type> for i32 { + fn as_(self) -> $type { + if self >= 0 { + $type::from(self as u32) + } else { + let abs = 0u32.wrapping_sub(self as u32); + $type::zero().wrapping_sub(&$type::from(abs)) + } + } + } + + impl AsPrimitive<$type> for i64 { + fn as_(self) -> $type { + if self >= 0 { + $type::from(self as u64) + } else { + let abs = 0u64.wrapping_sub(self as u64); + $type::zero().wrapping_sub(&$type::from(abs)) + } + } + } + + impl AsPrimitive<$type> for u8 { + fn as_(self) -> $type { + $type::from(self) + } + } + + impl AsPrimitive<$type> for u32 { + fn as_(self) -> $type { + $type::from(self) + } + } + + impl AsPrimitive<$type> for u64 { + fn as_(self) -> $type { + $type::from(self) + } + } + + impl AsPrimitive for $type { + fn as_(self) -> i32 { + self.0[0] as i32 + } + } + + impl AsPrimitive for $type { + fn as_(self) -> i64 { + self.0[0] as i64 + } + } + + impl AsPrimitive for $type { + fn as_(self) -> u8 { + self.0[0] as u8 + } + } + + impl AsPrimitive for $type { + fn as_(self) -> u32 { + self.0[0] as u32 + } + } + + impl AsPrimitive for $type { + fn as_(self) -> u64 { + self.0[0] + } + } + + #[cfg(test)] + mod $test_mod { + use super::*; + + #[test] + fn test_div_mod_floor() { + assert_eq!($type::from(10).div_floor(&$type::from(3)), $type::from(3)); + assert_eq!($type::from(10).mod_floor(&$type::from(3)), $type::from(1)); + assert_eq!( + $type::from(10).div_mod_floor(&$type::from(3)), + ($type::from(3), $type::from(1)) + ); + assert_eq!($type::from(5).div_floor(&$type::from(5)), $type::from(1)); + assert_eq!($type::from(5).mod_floor(&$type::from(5)), $type::from(0)); + assert_eq!( + $type::from(5).div_mod_floor(&$type::from(5)), + ($type::from(1), $type::from(0)) + ); + assert_eq!($type::from(3).div_floor(&$type::from(7)), $type::from(0)); + assert_eq!($type::from(3).mod_floor(&$type::from(7)), $type::from(3)); + assert_eq!( + $type::from(3).div_mod_floor(&$type::from(7)), + ($type::from(0), $type::from(3)) + ); + } + + #[test] + fn test_gcd() { + assert_eq!($type::from(10).gcd(&$type::from(2)), $type::from(2)); + assert_eq!($type::from(10).gcd(&$type::from(3)), $type::from(1)); + assert_eq!($type::from(0).gcd(&$type::from(3)), $type::from(3)); + assert_eq!($type::from(3).gcd(&$type::from(3)), $type::from(3)); + assert_eq!($type::from(56).gcd(&$type::from(42)), $type::from(14)); + assert_eq!( + $type::MAX.gcd(&($type::MAX / $type::from(2))), + $type::from(1) + ); + assert_eq!($type::from(15).gcd(&$type::from(17)), $type::from(1)); + } + + #[test] + fn test_lcm() { + assert_eq!($type::from(1).lcm(&$type::from(0)), $type::from(0)); + assert_eq!($type::from(0).lcm(&$type::from(1)), $type::from(0)); + assert_eq!($type::from(1).lcm(&$type::from(1)), $type::from(1)); + assert_eq!($type::from(8).lcm(&$type::from(9)), $type::from(72)); + assert_eq!($type::from(11).lcm(&$type::from(5)), $type::from(55)); + assert_eq!($type::from(15).lcm(&$type::from(17)), $type::from(255)); + assert_eq!($type::from(4).lcm(&$type::from(8)), $type::from(8)); + } + + #[test] + fn test_is_multiple_of() { + assert!($type::from(6).is_multiple_of(&$type::from(6))); + assert!($type::from(6).is_multiple_of(&$type::from(3))); + assert!($type::from(6).is_multiple_of(&$type::from(1))); + assert!(!$type::from(3).is_multiple_of(&$type::from(5))) + } + + #[test] + fn is_even() { + assert_eq!($type::from(0).is_even(), true); + assert_eq!($type::from(1).is_even(), false); + assert_eq!($type::from(2).is_even(), true); + assert_eq!($type::from(3).is_even(), false); + assert_eq!($type::from(4).is_even(), true); + } + + #[test] + fn is_odd() { + assert_eq!($type::from(0).is_odd(), false); + assert_eq!($type::from(1).is_odd(), true); + assert_eq!($type::from(2).is_odd(), false); + assert_eq!($type::from(3).is_odd(), true); + assert_eq!($type::from(4).is_odd(), false); + } + + #[test] + #[should_panic] + fn overflow_mul_test() { + let _ = $type::MAX * $type::from(2); + } + + #[test] + #[should_panic] + fn overflow_add_test() { + let _ = $type::MAX + $type::from(1); + } + + #[test] + #[should_panic] + fn underflow_sub_test() { + let _ = $type::zero() - $type::from(1); + } + } + }; +} + +impl_traits_for_uint!(U128, 16, u128_test); +impl_traits_for_uint!(U256, 32, u256_test); +impl_traits_for_uint!(U512, 64, u512_test); + +impl AsPrimitive for U128 { + fn as_(self) -> U128 { + self + } +} + +impl AsPrimitive for U128 { + fn as_(self) -> U256 { + let mut result = U256::zero(); + result.0[..2].clone_from_slice(&self.0[..2]); + result + } +} + +impl AsPrimitive for U128 { + fn as_(self) -> U512 { + let mut result = U512::zero(); + result.0[..2].clone_from_slice(&self.0[..2]); + result + } +} + +impl AsPrimitive for U256 { + fn as_(self) -> U128 { + let mut result = U128::zero(); + result.0[..2].clone_from_slice(&self.0[..2]); + result + } +} + +impl AsPrimitive for U256 { + fn as_(self) -> U256 { + self + } +} + +impl AsPrimitive for U256 { + fn as_(self) -> U512 { + let mut result = U512::zero(); + result.0[..4].clone_from_slice(&self.0[..4]); + result + } +} + +impl AsPrimitive for U512 { + fn as_(self) -> U128 { + let mut result = U128::zero(); + result.0[..2].clone_from_slice(&self.0[..2]); + result + } +} + +impl AsPrimitive for U512 { + fn as_(self) -> U256 { + let mut result = U256::zero(); + result.0[..4].clone_from_slice(&self.0[..4]); + result + } +} + +impl AsPrimitive for U512 { + fn as_(self) -> U512 { + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn check_as_i32>(expected: i32, input: T) { + assert_eq!(expected, input.as_()); + } + + fn check_as_i64>(expected: i64, input: T) { + assert_eq!(expected, input.as_()); + } + + fn check_as_u8>(expected: u8, input: T) { + assert_eq!(expected, input.as_()); + } + + fn check_as_u32>(expected: u32, input: T) { + assert_eq!(expected, input.as_()); + } + + fn check_as_u64>(expected: u64, input: T) { + assert_eq!(expected, input.as_()); + } + + fn check_as_u128>(expected: U128, input: T) { + assert_eq!(expected, input.as_()); + } + + fn check_as_u256>(expected: U256, input: T) { + assert_eq!(expected, input.as_()); + } + + fn check_as_u512>(expected: U512, input: T) { + assert_eq!(expected, input.as_()); + } + + #[test] + fn as_primitive_from_i32() { + let mut input = 0_i32; + check_as_i32(0, input); + check_as_i64(0, input); + check_as_u8(0, input); + check_as_u32(0, input); + check_as_u64(0, input); + check_as_u128(U128::zero(), input); + check_as_u256(U256::zero(), input); + check_as_u512(U512::zero(), input); + + input = i32::max_value() - 1; + check_as_i32(input, input); + check_as_i64(i64::from(input), input); + check_as_u8(input as u8, input); + check_as_u32(input as u32, input); + check_as_u64(input as u64, input); + check_as_u128(U128::from(input), input); + check_as_u256(U256::from(input), input); + check_as_u512(U512::from(input), input); + + input = i32::min_value() + 1; + check_as_i32(input, input); + check_as_i64(i64::from(input), input); + check_as_u8(input as u8, input); + check_as_u32(input as u32, input); + check_as_u64(input as u64, input); + // i32::min_value() is -1 - i32::max_value() + check_as_u128( + U128::zero().wrapping_sub(&U128::from(i32::max_value())), + input, + ); + check_as_u256( + U256::zero().wrapping_sub(&U256::from(i32::max_value())), + input, + ); + check_as_u512( + U512::zero().wrapping_sub(&U512::from(i32::max_value())), + input, + ); + } + + #[test] + fn as_primitive_from_i64() { + let mut input = 0_i64; + check_as_i32(0, input); + check_as_i64(0, input); + check_as_u8(0, input); + check_as_u32(0, input); + check_as_u64(0, input); + check_as_u128(U128::zero(), input); + check_as_u256(U256::zero(), input); + check_as_u512(U512::zero(), input); + + input = i64::max_value() - 1; + check_as_i32(input as i32, input); + check_as_i64(input, input); + check_as_u8(input as u8, input); + check_as_u32(input as u32, input); + check_as_u64(input as u64, input); + check_as_u128(U128::from(input), input); + check_as_u256(U256::from(input), input); + check_as_u512(U512::from(input), input); + + input = i64::min_value() + 1; + check_as_i32(input as i32, input); + check_as_i64(input, input); + check_as_u8(input as u8, input); + check_as_u32(input as u32, input); + check_as_u64(input as u64, input); + // i64::min_value() is (-1 - i64::max_value()) + check_as_u128( + U128::zero().wrapping_sub(&U128::from(i64::max_value())), + input, + ); + check_as_u256( + U256::zero().wrapping_sub(&U256::from(i64::max_value())), + input, + ); + check_as_u512( + U512::zero().wrapping_sub(&U512::from(i64::max_value())), + input, + ); + } + + #[test] + fn as_primitive_from_u8() { + let mut input = 0_u8; + check_as_i32(0, input); + check_as_i64(0, input); + check_as_u8(0, input); + check_as_u32(0, input); + check_as_u64(0, input); + check_as_u128(U128::zero(), input); + check_as_u256(U256::zero(), input); + check_as_u512(U512::zero(), input); + + input = u8::max_value() - 1; + check_as_i32(i32::from(input), input); + check_as_i64(i64::from(input), input); + check_as_u8(input, input); + check_as_u32(u32::from(input), input); + check_as_u64(u64::from(input), input); + check_as_u128(U128::from(input), input); + check_as_u256(U256::from(input), input); + check_as_u512(U512::from(input), input); + } + + #[test] + fn as_primitive_from_u32() { + let mut input = 0_u32; + check_as_i32(0, input); + check_as_i64(0, input); + check_as_u8(0, input); + check_as_u32(0, input); + check_as_u64(0, input); + check_as_u128(U128::zero(), input); + check_as_u256(U256::zero(), input); + check_as_u512(U512::zero(), input); + + input = u32::max_value() - 1; + check_as_i32(input as i32, input); + check_as_i64(i64::from(input), input); + check_as_u8(input as u8, input); + check_as_u32(input, input); + check_as_u64(u64::from(input), input); + check_as_u128(U128::from(input), input); + check_as_u256(U256::from(input), input); + check_as_u512(U512::from(input), input); + } + + #[test] + fn as_primitive_from_u64() { + let mut input = 0_u64; + check_as_i32(0, input); + check_as_i64(0, input); + check_as_u8(0, input); + check_as_u32(0, input); + check_as_u64(0, input); + check_as_u128(U128::zero(), input); + check_as_u256(U256::zero(), input); + check_as_u512(U512::zero(), input); + + input = u64::max_value() - 1; + check_as_i32(input as i32, input); + check_as_i64(input as i64, input); + check_as_u8(input as u8, input); + check_as_u32(input as u32, input); + check_as_u64(input, input); + check_as_u128(U128::from(input), input); + check_as_u256(U256::from(input), input); + check_as_u512(U512::from(input), input); + } + + fn make_little_endian_arrays(little_endian_bytes: &[u8]) -> ([u8; 4], [u8; 8]) { + let le_32 = { + let mut le_32 = [0; 4]; + le_32.copy_from_slice(&little_endian_bytes[..4]); + le_32 + }; + + let le_64 = { + let mut le_64 = [0; 8]; + le_64.copy_from_slice(&little_endian_bytes[..8]); + le_64 + }; + + (le_32, le_64) + } + + #[test] + fn as_primitive_from_u128() { + let mut input = U128::zero(); + check_as_i32(0, input); + check_as_i64(0, input); + check_as_u8(0, input); + check_as_u32(0, input); + check_as_u64(0, input); + check_as_u128(U128::zero(), input); + check_as_u256(U256::zero(), input); + check_as_u512(U512::zero(), input); + + input = U128::max_value() - 1; + + let mut little_endian_bytes = [0_u8; 64]; + input.to_little_endian(&mut little_endian_bytes[..16]); + let (le_32, le_64) = make_little_endian_arrays(&little_endian_bytes); + + check_as_i32(i32::from_le_bytes(le_32), input); + check_as_i64(i64::from_le_bytes(le_64), input); + check_as_u8(little_endian_bytes[0], input); + check_as_u32(u32::from_le_bytes(le_32), input); + check_as_u64(u64::from_le_bytes(le_64), input); + check_as_u128(U128::from_little_endian(&little_endian_bytes[..16]), input); + check_as_u256(U256::from_little_endian(&little_endian_bytes[..32]), input); + check_as_u512(U512::from_little_endian(&little_endian_bytes), input); + } + + #[test] + fn as_primitive_from_u256() { + let mut input = U256::zero(); + check_as_i32(0, input); + check_as_i64(0, input); + check_as_u8(0, input); + check_as_u32(0, input); + check_as_u64(0, input); + check_as_u128(U128::zero(), input); + check_as_u256(U256::zero(), input); + check_as_u512(U512::zero(), input); + + input = U256::max_value() - 1; + + let mut little_endian_bytes = [0_u8; 64]; + input.to_little_endian(&mut little_endian_bytes[..32]); + let (le_32, le_64) = make_little_endian_arrays(&little_endian_bytes); + + check_as_i32(i32::from_le_bytes(le_32), input); + check_as_i64(i64::from_le_bytes(le_64), input); + check_as_u8(little_endian_bytes[0], input); + check_as_u32(u32::from_le_bytes(le_32), input); + check_as_u64(u64::from_le_bytes(le_64), input); + check_as_u128(U128::from_little_endian(&little_endian_bytes[..16]), input); + check_as_u256(U256::from_little_endian(&little_endian_bytes[..32]), input); + check_as_u512(U512::from_little_endian(&little_endian_bytes), input); + } + + #[test] + fn as_primitive_from_u512() { + let mut input = U512::zero(); + check_as_i32(0, input); + check_as_i64(0, input); + check_as_u8(0, input); + check_as_u32(0, input); + check_as_u64(0, input); + check_as_u128(U128::zero(), input); + check_as_u256(U256::zero(), input); + check_as_u512(U512::zero(), input); + + input = U512::max_value() - 1; + + let mut little_endian_bytes = [0_u8; 64]; + input.to_little_endian(&mut little_endian_bytes); + let (le_32, le_64) = make_little_endian_arrays(&little_endian_bytes); + + check_as_i32(i32::from_le_bytes(le_32), input); + check_as_i64(i64::from_le_bytes(le_64), input); + check_as_u8(little_endian_bytes[0], input); + check_as_u32(u32::from_le_bytes(le_32), input); + check_as_u64(u64::from_le_bytes(le_64), input); + check_as_u128(U128::from_little_endian(&little_endian_bytes[..16]), input); + check_as_u256(U256::from_little_endian(&little_endian_bytes[..32]), input); + check_as_u512(U512::from_little_endian(&little_endian_bytes), input); + } + + #[test] + fn wrapping_test_u512() { + let max = U512::max_value(); + let value = max.wrapping_add(&1.into()); + assert_eq!(value, 0.into()); + + let min = U512::min_value(); + let value = min.wrapping_sub(&1.into()); + assert_eq!(value, U512::max_value()); + } + + #[test] + fn wrapping_test_u256() { + let max = U256::max_value(); + let value = max.wrapping_add(&1.into()); + assert_eq!(value, 0.into()); + + let min = U256::min_value(); + let value = min.wrapping_sub(&1.into()); + assert_eq!(value, U256::max_value()); + } + + #[test] + fn wrapping_test_u128() { + let max = U128::max_value(); + let value = max.wrapping_add(&1.into()); + assert_eq!(value, 0.into()); + + let min = U128::min_value(); + let value = min.wrapping_sub(&1.into()); + assert_eq!(value, U128::max_value()); + } +} diff --git a/types/src/uref.rs b/types/src/uref.rs new file mode 100644 index 0000000000..e7091425de --- /dev/null +++ b/types/src/uref.rs @@ -0,0 +1,171 @@ +use alloc::{format, string::String, vec::Vec}; +use core::{ + convert::TryFrom, + fmt::{self, Debug, Display, Formatter}, +}; + +use hex_fmt::HexFmt; + +use crate::{bytesrepr, AccessRights, ApiError, Key, ACCESS_RIGHTS_SERIALIZED_LENGTH}; + +/// The number of bytes in a [`URef`] address. +pub const UREF_ADDR_LENGTH: usize = 32; + +/// The number of bytes in a serialized [`URef`] where the [`AccessRights`] are not `None`. +pub const UREF_SERIALIZED_LENGTH: usize = UREF_ADDR_LENGTH + ACCESS_RIGHTS_SERIALIZED_LENGTH; + +/// The address of a [`URef`](types::URef) (unforgeable reference) on the network. +pub type URefAddr = [u8; UREF_ADDR_LENGTH]; + +/// Represents an unforgeable reference, containing an address in the network's global storage and +/// the [`AccessRights`] of the reference. +/// +/// A `URef` can be used to index entities such as [`CLValue`](crate::CLValue)s, or smart contracts. +#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] +pub struct URef(URefAddr, AccessRights); + +impl URef { + /// Constructs a [`URef`] from an address and access rights. + pub fn new(address: URefAddr, access_rights: AccessRights) -> Self { + URef(address, access_rights) + } + + /// Returns the address of this [`URef`]. + pub fn addr(&self) -> URefAddr { + self.0 + } + + /// Returns the access rights of this [`URef`]. + pub fn access_rights(&self) -> AccessRights { + self.1 + } + + /// Returns a new [`URef`] with the same address and updated access rights. + pub fn with_access_rights(self, access_rights: AccessRights) -> Self { + URef(self.0, access_rights) + } + + /// Removes the access rights from this [`URef`]. + pub fn remove_access_rights(self) -> Self { + URef(self.0, AccessRights::NONE) + } + + /// Returns `true` if the access rights are `Some` and + /// [`is_readable`](AccessRights::is_readable) is `true` for them. + pub fn is_readable(self) -> bool { + self.1.is_readable() + } + + /// Returns a new [`URef`] with the same address and [`AccessRights::READ`] permission. + pub fn into_read(self) -> URef { + URef(self.0, AccessRights::READ) + } + + /// Returns a new [`URef`] with the same address and [`AccessRights::READ_ADD_WRITE`] + /// permission. + pub fn into_read_add_write(self) -> URef { + URef(self.0, AccessRights::READ_ADD_WRITE) + } + + /// Returns `true` if the access rights are `Some` and + /// [`is_writeable`](AccessRights::is_writeable) is `true` for them. + pub fn is_writeable(self) -> bool { + self.1.is_writeable() + } + + /// Returns `true` if the access rights are `Some` and [`is_addable`](AccessRights::is_addable) + /// is `true` for them. + pub fn is_addable(self) -> bool { + self.1.is_addable() + } + + /// Formats the address and access rights of the [`URef`] in an unique way that could be used as + /// a name when storing the given `URef` in a global state. + pub fn as_string(&self) -> String { + // Extract bits as numerical value, with no flags marked as 0. + let access_rights_bits = self.access_rights().bits(); + // Access rights is represented as octal, which means that max value of u8 can + // be represented as maximum of 3 octal digits. + format!( + "uref-{}-{:03o}", + base16::encode_lower(&self.addr()), + access_rights_bits + ) + } +} + +impl Display for URef { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let addr = self.addr(); + let access_rights = self.access_rights(); + write!(f, "URef({}, {})", HexFmt(&addr), access_rights) + } +} + +impl Debug for URef { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl bytesrepr::ToBytes for URef { + fn to_bytes(&self) -> Result, bytesrepr::Error> { + let mut result = bytesrepr::unchecked_allocate_buffer(self); + result.append(&mut self.0.to_bytes()?); + result.append(&mut self.1.to_bytes()?); + Ok(result) + } + + fn serialized_length(&self) -> usize { + UREF_SERIALIZED_LENGTH + } +} + +impl bytesrepr::FromBytes for URef { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { + let (id, rem): ([u8; 32], &[u8]) = bytesrepr::FromBytes::from_bytes(bytes)?; + let (access_rights, rem): (AccessRights, &[u8]) = bytesrepr::FromBytes::from_bytes(rem)?; + Ok((URef(id, access_rights), rem)) + } +} + +impl TryFrom for URef { + type Error = ApiError; + + fn try_from(key: Key) -> Result { + if let Key::URef(uref) = key { + Ok(uref) + } else { + Err(ApiError::UnexpectedKeyVariant) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn uref_as_string() { + // Since we are putting URefs to named_keys map keyed by the label that + // `as_string()` returns, any changes to the string representation of + // that type cannot break the format. + let addr_array = [0u8; 32]; + let uref_a = URef::new(addr_array, AccessRights::READ); + assert_eq!( + uref_a.as_string(), + "uref-0000000000000000000000000000000000000000000000000000000000000000-001" + ); + let uref_b = URef::new(addr_array, AccessRights::WRITE); + assert_eq!( + uref_b.as_string(), + "uref-0000000000000000000000000000000000000000000000000000000000000000-002" + ); + + let uref_c = uref_b.remove_access_rights(); + assert_eq!( + uref_c.as_string(), + "uref-0000000000000000000000000000000000000000000000000000000000000000-000" + ); + } +} diff --git a/types/tests/version_numbers.rs b/types/tests/version_numbers.rs new file mode 100644 index 0000000000..9f1d04aae3 --- /dev/null +++ b/types/tests/version_numbers.rs @@ -0,0 +1,4 @@ +#[test] +fn test_html_root_url() { + version_sync::assert_html_root_url_updated!("src/lib.rs"); +} From 2ce36ad30643bfd0fabd1a42800a3d5f832e3f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Papierski?= Date: Tue, 7 Jul 2020 12:26:17 +0200 Subject: [PATCH 2/8] NDRS-90: Replace failure with thiserror. Crate "types" still depends on failure since thiserror's support for no_std is not yet landed[1] [1]: https://github.com/dtolnay/thiserror/pull/64 --- Cargo.lock | 60 +------------- node/Cargo.toml | 6 +- node/src/contract_core/engine_state/error.rs | 43 +++++----- node/src/contract_core/execution/error.rs | 80 +++++++++---------- node/src/contract_core/resolvers/error.rs | 8 +- node/src/contract_storage/error/in_memory.rs | 10 +-- node/src/contract_storage/error/lmdb.rs | 20 ++--- .../trie_store/operations/tests/mod.rs | 8 +- smart-contracts/contract/Cargo.toml | 2 +- types/Cargo.toml | 1 + 10 files changed, 81 insertions(+), 157 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d11d9e3031..16aecee149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,21 +24,6 @@ dependencies = [ "casperlabs-types", ] -[[package]] -name = "addr2line" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602d785912f476e480434627e8732e6766b760c045bbf897d9dfaa9f4fbd399c" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler32" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" - [[package]] name = "aho-corasick" version = "0.7.13" @@ -145,20 +130,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -[[package]] -name = "backtrace" -version = "0.3.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base16" version = "0.2.1" @@ -340,8 +311,8 @@ name = "casperlabs-contract" version = "0.6.0" dependencies = [ "casperlabs-types", - "failure", "hex_fmt", + "thiserror", "version-sync", "wee_alloc", ] @@ -426,7 +397,6 @@ dependencies = [ "ed25519-dalek", "either", "enum-iterator", - "failure", "fake_instant", "futures 0.3.5", "getrandom", @@ -1171,7 +1141,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" dependencies = [ - "backtrace", "failure_derive", ] @@ -1467,12 +1436,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gimli" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" - [[package]] name = "glob" version = "0.2.11" @@ -2095,15 +2058,6 @@ dependencies = [ "unicase 2.6.0", ] -[[package]] -name = "miniz_oxide" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] - [[package]] name = "mint-install" version = "0.1.0" @@ -2381,12 +2335,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" - [[package]] name = "once_cell" version = "1.4.0" @@ -3315,12 +3263,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - [[package]] name = "rustc-hex" version = "2.1.0" diff --git a/node/Cargo.toml b/node/Cargo.toml index 451b4fc0e9..7c2a599d56 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -24,7 +24,7 @@ directories = "2.0.2" ed25519-dalek = { version = "1.0.0-pre.3", default-features = false, features = ["rand", "serde", "u64_backend"] } either = "1.5.3" enum-iterator = "0.6.0" -failure = "0.1.6" +thiserror = "1.0.18" futures = "0.3.5" getrandom = "0.1.14" hex = "0.4.2" @@ -50,7 +50,6 @@ serde-big-array = "0.3.0" smallvec = "1.4.0" structopt = "0.3.14" tempfile = "3.1.0" -thiserror = "1.0.18" tokio = { version = "0.2.20", features = ["macros", "rt-threaded", "sync", "tcp", "time", "blocking"] } tokio-openssl = "0.4.0" tokio-serde = { version = "0.6.1", features = ["messagepack"] } @@ -61,9 +60,6 @@ tracing-subscriber = "0.2.5" warp = "0.2.3" wasmi = "0.6.2" types = { path = "../types", package = "casperlabs-types", features = ["std", "gens"] } - -### - chrono = "0.4.10" hostname = "0.3.0" libc = "0.2.66" diff --git a/node/src/contract_core/engine_state/error.rs b/node/src/contract_core/engine_state/error.rs index 382cf86e9e..f251987541 100644 --- a/node/src/contract_core/engine_state/error.rs +++ b/node/src/contract_core/engine_state/error.rs @@ -1,4 +1,4 @@ -use failure::Fail; +use thiserror::Error; use crate::contract_shared::newtypes::Blake2bHash; use crate::contract_shared::wasm_prep; @@ -8,46 +8,43 @@ use types::{bytesrepr, system_contract_errors::mint}; use crate::contract_core::execution; use crate::contract_storage; -#[derive(Fail, Debug)] +#[derive(Error, Debug)] pub enum Error { - #[fail(display = "Invalid hash length: expected {}, actual {}", _0, _1)] + #[error("Invalid hash length: expected {expected}, actual {actual}")] InvalidHashLength { expected: usize, actual: usize }, - #[fail( - display = "Invalid account hash length: expected {}, actual {}", - _0, _1 - )] + #[error("Invalid account hash length: expected {expected}, actual {actual}")] InvalidAccountHashLength { expected: usize, actual: usize }, - #[fail(display = "Invalid protocol version: {}", _0)] + #[error("Invalid protocol version: {0}")] InvalidProtocolVersion(ProtocolVersion), - #[fail(display = "Invalid upgrade config")] + #[error("Invalid upgrade config")] InvalidUpgradeConfig, - #[fail(display = "Wasm preprocessing error: {}", _0)] + #[error("Wasm preprocessing error: {0}")] WasmPreprocessing(wasm_prep::PreprocessingError), - #[fail(display = "Wasm serialization error: {:?}", _0)] + #[error("Wasm serialization error: {0:?}")] WasmSerialization(parity_wasm::SerializationError), - #[fail(display = "{}", _0)] + #[error(transparent)] Exec(execution::Error), - #[fail(display = "Storage error: {}", _0)] + #[error("Storage error: {}", _0)] Storage(contract_storage::error::Error), - #[fail(display = "Authorization failure: not authorized.")] + #[error("Authorization failure: not authorized.")] Authorization, - #[fail(display = "Insufficient payment")] + #[error("Insufficient payment")] InsufficientPayment, - #[fail(display = "Deploy error")] + #[error("Deploy error")] Deploy, - #[fail(display = "Payment finalization error")] + #[error("Payment finalization error")] Finalization, - #[fail(display = "Missing system contract association: {}", _0)] + #[error("Missing system contract association: {}", _0)] MissingSystemContract(String), - #[fail(display = "Serialization error: {}", _0)] + #[error("Serialization error: {}", _0)] Serialization(bytesrepr::Error), - #[fail(display = "Mint error: {}", _0)] + #[error("Mint error: {}", _0)] Mint(mint::Error), - #[fail(display = "Unsupported key type: {}", _0)] + #[error("Unsupported key type: {}", _0)] InvalidKeyVariant(String), - #[fail(display = "Invalid upgrade result value")] + #[error("Invalid upgrade result value")] InvalidUpgradeResult, - #[fail(display = "Unsupported deploy item variant: {}", _0)] + #[error("Unsupported deploy item variant: {}", _0)] InvalidDeployItemVariant(String), } diff --git a/node/src/contract_core/execution/error.rs b/node/src/contract_core/execution/error.rs index 8833c62d03..871357997f 100644 --- a/node/src/contract_core/execution/error.rs +++ b/node/src/contract_core/execution/error.rs @@ -1,4 +1,4 @@ -use failure::Fail; +use thiserror::Error; use parity_wasm::elements; use crate::contract_shared::{wasm_prep, TypeMismatch}; @@ -11,84 +11,78 @@ use types::{ use crate::contract_core::resolvers::error::ResolverError; use crate::contract_storage; -#[derive(Fail, Debug, Clone)] +#[derive(Error, Debug, Clone)] pub enum Error { - #[fail(display = "Interpreter error: {}", _0)] + #[error("Interpreter error: {}", _0)] Interpreter(String), - #[fail(display = "Storage error: {}", _0)] + #[error("Storage error: {}", _0)] Storage(contract_storage::error::Error), - #[fail(display = "Serialization error: {}", _0)] + #[error("Serialization error: {}", _0)] BytesRepr(bytesrepr::Error), - #[fail(display = "Named key {} not found", _0)] + #[error("Named key {} not found", _0)] NamedKeyNotFound(String), - #[fail(display = "Key {} not found", _0)] + #[error("Key {} not found", _0)] KeyNotFound(Key), - #[fail(display = "Account {:?} not found", _0)] + #[error("Account {:?} not found", _0)] AccountNotFound(Key), - #[fail(display = "{}", _0)] + #[error("{}", _0)] TypeMismatch(TypeMismatch), - #[fail(display = "Invalid access rights: {}", required)] + #[error("Invalid access rights: {}", required)] InvalidAccess { required: AccessRights }, - #[fail(display = "Forged reference: {}", _0)] + #[error("Forged reference: {}", _0)] ForgedReference(URef), - #[fail(display = "URef not found: {}", _0)] + #[error("URef not found: {}", _0)] URefNotFound(String), - #[fail(display = "Function not found: {}", _0)] + #[error("Function not found: {}", _0)] FunctionNotFound(String), - #[fail(display = "{}", _0)] + #[error("{}", _0)] ParityWasm(elements::Error), - #[fail(display = "Out of gas error")] + #[error("Out of gas error")] GasLimit, - #[fail(display = "Return")] + #[error("Return")] Ret(Vec), - #[fail(display = "{}", _0)] + #[error("{}", _0)] Rng(String), - #[fail(display = "Resolver error: {}", _0)] + #[error("Resolver error: {}", _0)] Resolver(ResolverError), /// Reverts execution with a provided status - #[fail(display = "{}", _0)] + #[error("{}", _0)] Revert(ApiError), - #[fail(display = "{}", _0)] + #[error("{}", _0)] AddKeyFailure(AddKeyFailure), - #[fail(display = "{}", _0)] + #[error("{}", _0)] RemoveKeyFailure(RemoveKeyFailure), - #[fail(display = "{}", _0)] + #[error("{}", _0)] UpdateKeyFailure(UpdateKeyFailure), - #[fail(display = "{}", _0)] + #[error("{}", _0)] SetThresholdFailure(SetThresholdFailure), - #[fail(display = "{}", _0)] + #[error("{}", _0)] SystemContract(system_contract_errors::Error), - #[fail(display = "Deployment authorization failure")] + #[error("Deployment authorization failure")] DeploymentAuthorizationFailure, - #[fail(display = "Expected return value")] + #[error("Expected return value")] ExpectedReturnValue, - #[fail(display = "Unexpected return value")] + #[error("Unexpected return value")] UnexpectedReturnValue, - #[fail(display = "Invalid context")] + #[error("Invalid context")] InvalidContext, - #[fail( - display = "Incompatible protocol major version. Expected version {} but actual version is {}", - expected, actual - )] + #[error("Incompatible protocol major version. Expected version {expected} but actual version is {actual}")] IncompatibleProtocolMajorVersion { expected: u32, actual: u32 }, - #[fail(display = "{}", _0)] + #[error("{0}")] CLValue(CLValueError), - #[fail(display = "Host buffer is empty")] + #[error("Host buffer is empty")] HostBufferEmpty, - #[fail(display = "Unsupported WASM start")] + #[error("Unsupported WASM start")] UnsupportedWasmStart, - #[fail(display = "No active contract versions for contract package")] + #[error("No active contract versions for contract package")] NoActiveContractVersions(ContractPackageHash), - #[fail(display = "Invalid contract version: {}", _0)] + #[error("Invalid contract version: {}", _0)] InvalidContractVersion(ContractVersionKey), - #[fail(display = "No such method: {}", _0)] + #[error("No such method: {}", _0)] NoSuchMethod(String), - #[fail(display = "Wasm preprocessing error: {}", _0)] + #[error("Wasm preprocessing error: {}", _0)] WasmPreprocessing(wasm_prep::PreprocessingError), - #[fail( - display = "Unexpected Key length. Expected length {} but actual length is {}", - expected, actual - )] + #[error("Unexpected Key length. Expected length {expected} but actual length is {actual}")] InvalidKeyLength { expected: usize, actual: usize }, } diff --git a/node/src/contract_core/resolvers/error.rs b/node/src/contract_core/resolvers/error.rs index 7bf287ff5f..a4875cd35f 100644 --- a/node/src/contract_core/resolvers/error.rs +++ b/node/src/contract_core/resolvers/error.rs @@ -1,11 +1,11 @@ -use failure::Fail; +use thiserror::Error; use types::ProtocolVersion; -#[derive(Fail, Debug, Copy, Clone)] +#[derive(Error, Debug, Copy, Clone)] pub enum ResolverError { - #[fail(display = "Unknown protocol version: {}", _0)] + #[error("Unknown protocol version: {}", _0)] UnknownProtocolVersion(ProtocolVersion), - #[fail(display = "No imported memory")] + #[error("No imported memory")] NoImportedMemory, } diff --git a/node/src/contract_storage/error/in_memory.rs b/node/src/contract_storage/error/in_memory.rs index e3bedb3ded..23f00b5f85 100644 --- a/node/src/contract_storage/error/in_memory.rs +++ b/node/src/contract_storage/error/in_memory.rs @@ -1,15 +1,15 @@ use std::sync; -use failure::Fail; +use thiserror::Error; use types::bytesrepr; -#[derive(Debug, Fail, PartialEq, Eq)] +#[derive(Debug, Error, PartialEq, Eq)] pub enum Error { - #[fail(display = "{}", _0)] - BytesRepr(#[fail(cause)] bytesrepr::Error), + #[error("{0}")] + BytesRepr(bytesrepr::Error), - #[fail(display = "Another thread panicked while holding a lock")] + #[error("Another thread panicked while holding a lock")] Poison, } diff --git a/node/src/contract_storage/error/lmdb.rs b/node/src/contract_storage/error/lmdb.rs index da06c4faef..8df5e94dd6 100644 --- a/node/src/contract_storage/error/lmdb.rs +++ b/node/src/contract_storage/error/lmdb.rs @@ -1,32 +1,26 @@ use std::sync; -use failure::Fail; +use thiserror::Error; use lmdb as lmdb_external; use types::bytesrepr; use super::in_memory; -#[derive(Debug, Clone, Fail, PartialEq, Eq)] +#[derive(Debug, Clone, Error, PartialEq, Eq)] pub enum Error { - #[fail(display = "{}", _0)] - Lmdb(#[fail(cause)] lmdb_external::Error), + #[error(transparent)] + Lmdb(#[from] lmdb_external::Error), - #[fail(display = "{}", _0)] - BytesRepr(#[fail(cause)] bytesrepr::Error), + #[error("{0}")] + BytesRepr(bytesrepr::Error), - #[fail(display = "Another thread panicked while holding a lock")] + #[error("Another thread panicked while holding a lock")] Poison, } impl wasmi::HostError for Error {} -impl From for Error { - fn from(error: lmdb_external::Error) -> Self { - Error::Lmdb(error) - } -} - impl From for Error { fn from(error: bytesrepr::Error) -> Self { Error::BytesRepr(error) diff --git a/node/src/contract_storage/trie_store/operations/tests/mod.rs b/node/src/contract_storage/trie_store/operations/tests/mod.rs index 211d6f9b47..42045bdb71 100644 --- a/node/src/contract_storage/trie_store/operations/tests/mod.rs +++ b/node/src/contract_storage/trie_store/operations/tests/mod.rs @@ -510,7 +510,7 @@ struct LmdbTestContext { } impl LmdbTestContext { - fn new(tries: &[HashedTrie]) -> Result + fn new(tries: &[HashedTrie]) -> anyhow::Result where K: FromBytes + ToBytes, V: FromBytes + ToBytes, @@ -526,7 +526,7 @@ impl LmdbTestContext { }) } - fn update(&self, tries: &[HashedTrie]) -> Result<(), failure::Error> + fn update(&self, tries: &[HashedTrie]) -> anyhow::Result<()> where K: ToBytes, V: ToBytes, @@ -543,7 +543,7 @@ struct InMemoryTestContext { } impl InMemoryTestContext { - fn new(tries: &[HashedTrie]) -> Result + fn new(tries: &[HashedTrie]) -> anyhow::Result where K: ToBytes, V: ToBytes, @@ -554,7 +554,7 @@ impl InMemoryTestContext { Ok(InMemoryTestContext { environment, store }) } - fn update(&self, tries: &[HashedTrie]) -> Result<(), failure::Error> + fn update(&self, tries: &[HashedTrie]) -> anyhow::Result<()> where K: ToBytes, V: ToBytes, diff --git a/smart-contracts/contract/Cargo.toml b/smart-contracts/contract/Cargo.toml index 6edc0c5047..648cd0250e 100644 --- a/smart-contracts/contract/Cargo.toml +++ b/smart-contracts/contract/Cargo.toml @@ -18,7 +18,7 @@ no-unstable-features = ["std", "casperlabs-types/no-unstable-features"] [dependencies] casperlabs-types = { version = "0.6.0", path = "../../types" } -failure = { version = "0.1.6", default-features = false, features = ["failure_derive"] } +thiserror = "1.0.18" hex_fmt = "0.3.0" wee_alloc = "0.4.5" diff --git a/types/Cargo.toml b/types/Cargo.toml index d88af9d8fb..7b98a0c8a4 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -20,6 +20,7 @@ no-unstable-features = [] base16 = { version = "0.2.1", default-features = false } bitflags = "1" blake2 = { version = "0.8.1", default-features = false } +# TODO: Replace failure with thiserror once no_std support is landed https://github.com/dtolnay/thiserror/pull/64 failure = { version = "0.1.6", default-features = false, features = ["failure_derive"] } hex_fmt = "0.3.0" num-derive = { version = "0.3.0", default-features = false } From 6599e34caf16bff64f4505100d18a97019df8ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Papierski?= Date: Tue, 7 Jul 2020 12:27:07 +0200 Subject: [PATCH 3/8] NDRS-90: Make metrics logger noop. Those will be revisited as part of https://casperlabs.atlassian.net/browse/NDRS-120 story. --- grpc/tests/src/logging/metrics.rs | 75 +--------------- node/src/contract_core/execution/error.rs | 2 +- node/src/contract_shared/logging/mod.rs | 105 +++------------------- node/src/contract_storage/error/lmdb.rs | 2 +- 4 files changed, 18 insertions(+), 166 deletions(-) diff --git a/grpc/tests/src/logging/metrics.rs b/grpc/tests/src/logging/metrics.rs index ca23dbd1e9..bbad57037d 100644 --- a/grpc/tests/src/logging/metrics.rs +++ b/grpc/tests/src/logging/metrics.rs @@ -1,34 +1,14 @@ use std::sync::{Arc, Mutex}; -use log::{LevelFilter, Metadata, Record}; -use serde_json::Value; +use log::{Metadata, Record}; -use engine_test_support::internal::{InMemoryWasmTestBuilder, MOCKED_ACCOUNT_ADDRESS}; -use node::contract_core::engine_state::EngineConfig; -use node::contract_shared::{ - logging::{self, Settings, TerminalLogger, PAYLOAD_KEY}, - newtypes::CorrelationId, - test_utils, -}; -use node::contract_storage::global_state::in_memory::InMemoryGlobalState; - -const PROPERTIES_KEY: &str = "properties"; -const CORRELATION_ID_KEY: &str = "correlation_id"; +use node::contract_shared::logging::TerminalLogger; struct Logger { terminal_logger: TerminalLogger, log_lines: Arc>>, } -impl Logger { - fn new(buffer: Arc>>, settings: &Settings) -> Self { - Logger { - terminal_logger: TerminalLogger::new(settings), - log_lines: buffer, - } - } -} - impl log::Log for Logger { fn enabled(&self, metadata: &Metadata) -> bool { self.terminal_logger.enabled(metadata) @@ -42,54 +22,3 @@ impl log::Log for Logger { fn flush(&self) {} } - -fn extract_correlation_id_property(line: &str) -> Option { - if let Some(idx) = line.find(PAYLOAD_KEY) { - let start = idx + PAYLOAD_KEY.len(); - let end = line.len(); - let slice = &line[start..end]; - serde_json::from_str::(slice) - .ok() - .and_then(|full_value| full_value.get(PROPERTIES_KEY).cloned()) - .and_then(|properties_value| properties_value.get(CORRELATION_ID_KEY).cloned()) - .and_then(|correlation_id_value| correlation_id_value.as_str().map(String::from)) - } else { - None - } -} - -#[test] -fn should_commit_with_metrics() { - let settings = Settings::new(LevelFilter::Trace).with_metrics_enabled(true); - let log_lines = Arc::new(Mutex::new(vec![])); - let logger = Box::new(Logger::new(Arc::clone(&log_lines), &settings)); - let _ = logging::initialize_with_logger(logger, settings); - - let correlation_id = CorrelationId::new(); - let mocked_account = test_utils::mocked_account(MOCKED_ACCOUNT_ADDRESS); - let (global_state, root_hash) = - InMemoryGlobalState::from_pairs(correlation_id, &mocked_account).unwrap(); - - let engine_config = EngineConfig::new(); - - let result = - InMemoryWasmTestBuilder::new(global_state, engine_config, root_hash.to_vec()).finish(); - - let _commit_response = result - .builder() - .commit_transforms(root_hash.to_vec(), Default::default()); - - let mut log_lines = log_lines.lock().unwrap(); - - let expected_fragment = format!(r#""{}":"{}""#, CORRELATION_ID_KEY, correlation_id); - log_lines.retain(|line| line.contains(&expected_fragment)); - assert!( - !log_lines.is_empty(), - "at least one log line should contain the expected correlation ID" - ); - - for line in log_lines.iter() { - let extracted_correlation_id = extract_correlation_id_property(line).unwrap(); - assert_eq!(correlation_id.to_string(), extracted_correlation_id); - } -} diff --git a/node/src/contract_core/execution/error.rs b/node/src/contract_core/execution/error.rs index 871357997f..d3fa78b20d 100644 --- a/node/src/contract_core/execution/error.rs +++ b/node/src/contract_core/execution/error.rs @@ -1,5 +1,5 @@ -use thiserror::Error; use parity_wasm::elements; +use thiserror::Error; use crate::contract_shared::{wasm_prep, TypeMismatch}; use types::{ diff --git a/node/src/contract_shared/logging/mod.rs b/node/src/contract_shared/logging/mod.rs index da0bcc04c9..36e893c609 100644 --- a/node/src/contract_shared/logging/mod.rs +++ b/node/src/contract_shared/logging/mod.rs @@ -4,12 +4,9 @@ mod settings; mod structured_message; mod terminal_logger; -use std::{ - collections::BTreeMap, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; +use std::{collections::BTreeMap, time::Duration}; -use log::{self, Level, LevelFilter, Log, Metadata, Record, SetLoggerError}; +use log::{self, Level, LevelFilter, Log, SetLoggerError}; pub use self::terminal_logger::TerminalLogger; use crate::contract_shared::newtypes::CorrelationId; @@ -60,29 +57,11 @@ pub fn initialize_with_logger( /// * `properties` - a collection of machine readable key / value properties which will be logged #[inline] pub fn log_details( - log_level: Level, - message_format: String, - mut properties: BTreeMap<&str, String>, + _log_level: Level, + _message_format: String, + _properties: BTreeMap<&str, String>, ) { - let logger = log::logger(); - - let metadata = Metadata::builder() - .target(CASPERLABS_METADATA_TARGET) - .level(log_level) - .build(); - - if !logger.enabled(&metadata) { - return; - } - - properties.insert(MESSAGE_TEMPLATE_KEY, message_format); - - let record = Record::builder() - .metadata(metadata) - .key_values(&properties) - .build(); - - logger.log(&record); + // TODO: Metrics story https://casperlabs.atlassian.net/browse/NDRS-120 } /// Logs the duration of a specific operation. @@ -117,72 +96,16 @@ pub fn log_duration(correlation_id: CorrelationId, metric: &str, tag: &str, dura /// * `metric_value` - numeric value of metric #[inline] pub fn log_metric( - correlation_id: CorrelationId, - metric: &str, - tag: &str, - metric_key: &str, - metric_value: f64, + _correlation_id: CorrelationId, + _metric: &str, + _tag: &str, + _metric_key: &str, + _metric_value: f64, ) { - let logger = log::logger(); - - let metadata = Metadata::builder() - .target(METRIC_METADATA_TARGET) - .level(Level::Info) - .build(); - - if !logger.enabled(&metadata) { - return; - } - - let from_epoch = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("UNIX EPOCH ERROR"); - - let milliseconds_since_epoch = from_epoch.as_millis() as i64; - - // https://prometheus.io/docs/instrumenting/exposition_formats/ - let tsd_metric = format!( - "{}{{tag=\"{}\"}} {} {:?}", - metric, tag, metric_value, milliseconds_since_epoch - ); - - let mut properties = BTreeMap::new(); - properties.insert("correlation_id", correlation_id.to_string()); - properties.insert("time-series-data", tsd_metric); - properties.insert(metric_key, format!("{:?}", metric_value)); - properties.insert( - DEFAULT_MESSAGE_KEY, - format!("{} {} {}", metric, tag, metric_value), - ); - - let record = Record::builder() - .metadata(metadata) - .key_values(&properties) - .build(); - - logger.log(&record); + // TODO: Metrics story https://casperlabs.atlassian.net/browse/NDRS-120 } /// Logs the metrics associated with the specified host function. -pub fn log_host_function_metrics(host_function: &str, mut properties: BTreeMap<&str, String>) { - let logger = log::logger(); - - let metadata = Metadata::builder() - .target(METRIC_METADATA_TARGET) - .level(Level::Info) - .build(); - - if !logger.enabled(&metadata) { - return; - } - - let default_message = format!("{} {:?}", host_function, properties); - properties.insert(DEFAULT_MESSAGE_KEY, default_message); - - let record = Record::builder() - .metadata(metadata) - .key_values(&properties) - .build(); - - logger.log(&record); +pub fn log_host_function_metrics(_host_function: &str, _properties: BTreeMap<&str, String>) { + // TODO: Metrics story https://casperlabs.atlassian.net/browse/NDRS-120 } diff --git a/node/src/contract_storage/error/lmdb.rs b/node/src/contract_storage/error/lmdb.rs index 8df5e94dd6..26d334bf95 100644 --- a/node/src/contract_storage/error/lmdb.rs +++ b/node/src/contract_storage/error/lmdb.rs @@ -1,7 +1,7 @@ use std::sync; -use thiserror::Error; use lmdb as lmdb_external; +use thiserror::Error; use types::bytesrepr; From 226373ac953fa76a26ed8c27165c0eb83c7c9622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Papierski?= Date: Tue, 7 Jul 2020 12:27:23 +0200 Subject: [PATCH 4/8] NDRS-90: Proper configuration for contract runtime --- grpc/server/src/main.rs | 2 +- .../src/internal/wasm_test_builder.rs | 4 +-- node/src/components/contract_runtime.rs | 15 ++++++---- .../src/components/contract_runtime/config.rs | 29 +++++++++++++++++++ node/src/components/storage.rs | 6 ++-- node/src/components/storage/config.rs | 23 ++------------- node/src/contract_shared/page_size.rs | 28 +++++++++++++++++- node/src/contract_storage.rs | 2 +- node/src/lib.rs | 1 + node/src/reactor/validator.rs | 9 ++++-- node/src/reactor/validator/config.rs | 5 +++- 11 files changed, 88 insertions(+), 36 deletions(-) create mode 100644 node/src/components/contract_runtime/config.rs diff --git a/grpc/server/src/main.rs b/grpc/server/src/main.rs index 635b4b830b..099705b76e 100644 --- a/grpc/server/src/main.rs +++ b/grpc/server/src/main.rs @@ -282,7 +282,7 @@ fn get_data_dir(arg_matches: &ArgMatches) -> PathBuf { /// Parses pages argument and returns map size fn get_map_size(arg_matches: &ArgMatches) -> usize { - let page_size = page_size::get_page_size().unwrap(); + let page_size = *page_size::PAGE_SIZE; let pages = arg_matches .value_of(ARG_PAGES) .map_or(Ok(DEFAULT_PAGES), usize::from_str) diff --git a/grpc/test-support/src/internal/wasm_test_builder.rs b/grpc/test-support/src/internal/wasm_test_builder.rs index 8ce43a9f27..665adb1906 100644 --- a/grpc/test-support/src/internal/wasm_test_builder.rs +++ b/grpc/test-support/src/internal/wasm_test_builder.rs @@ -178,7 +178,7 @@ impl LmdbWasmTestBuilder { engine_config: EngineConfig, ) -> Self { Self::initialize_logging(); - let page_size = page_size::get_page_size().expect("should get page size"); + let page_size = *page_size::PAGE_SIZE; let global_state_dir = Self::create_and_get_global_state_dir(data_dir); let environment = Arc::new( LmdbEnvironment::new(&global_state_dir, page_size * DEFAULT_LMDB_PAGES) @@ -241,7 +241,7 @@ impl LmdbWasmTestBuilder { post_state_hash: Vec, ) -> Self { Self::initialize_logging(); - let page_size = page_size::get_page_size().expect("should get page size"); + let page_size = *page_size::PAGE_SIZE; let global_state_dir = Self::create_and_get_global_state_dir(data_dir); let environment = Arc::new( LmdbEnvironment::new(&global_state_dir, page_size * DEFAULT_LMDB_PAGES) diff --git a/node/src/components/contract_runtime.rs b/node/src/components/contract_runtime.rs index 334906eaf7..5eb3e54c78 100644 --- a/node/src/components/contract_runtime.rs +++ b/node/src/components/contract_runtime.rs @@ -1,4 +1,6 @@ //! Contract Runtime component. +mod config; + use std::{ fmt::{Debug, Display}, path::PathBuf, @@ -10,7 +12,6 @@ use rand::Rng; use serde::{Deserialize, Serialize}; use crate::contract_core::engine_state::{EngineConfig, EngineState}; -use crate::contract_shared::page_size; use crate::contract_storage::protocol_data_store::lmdb::LmdbProtocolDataStore; use crate::contract_storage::{ global_state::lmdb::LmdbGlobalState, transaction_source::lmdb::LmdbEnvironment, @@ -19,6 +20,8 @@ use crate::contract_storage::{ use crate::components::Component; use crate::effect::{Effect, EffectBuilder, Multiple}; +use crate::StorageConfig; +pub use config::Config; /// The contract runtime components. pub(crate) struct ContractRuntime { @@ -105,15 +108,17 @@ fn get_engine_state( impl ContractRuntime { /// Create and initialize a new pinger. pub(crate) fn new + Send>( + storage_config: &StorageConfig, + contract_runtime_config: Config, _effect_builder: EffectBuilder, ) -> (Self, Multiple>) { let engine_config = EngineConfig::new() - .with_use_system_contracts(false) - .with_enable_bonding(false); + .with_use_system_contracts(contract_runtime_config.use_system_contracts) + .with_enable_bonding(contract_runtime_config.enable_bonding); let engine_state = get_engine_state( - PathBuf::from("/tmp"), - page_size::get_page_size().expect("should get page size"), + storage_config.path.clone(), + contract_runtime_config.map_size, engine_config, ); diff --git a/node/src/components/contract_runtime/config.rs b/node/src/components/contract_runtime/config.rs new file mode 100644 index 0000000000..1277dabefc --- /dev/null +++ b/node/src/components/contract_runtime/config.rs @@ -0,0 +1,29 @@ +use crate::contract_shared::page_size; +use serde::{Deserialize, Serialize}; + +// 750 GiB = 805306368000 bytes +// page size on x86_64 linux = 4096 bytes +// 805306368000 / 4096 = 196608000 +const DEFAULT_PAGES: usize = 196_608_000; + +/// Contract runtime configuration. +#[derive(Debug, Deserialize, Serialize)] +pub struct Config { + /// Storage + pub use_system_contracts: bool, + /// Enables + pub enable_bonding: bool, + /// Map size + #[serde(deserialize_with = "page_size::deserialize_page_size_multiple")] + pub map_size: usize, +} + +impl Default for Config { + fn default() -> Self { + Config { + use_system_contracts: false, + enable_bonding: false, + map_size: DEFAULT_PAGES * *page_size::PAGE_SIZE, + } + } +} diff --git a/node/src/components/storage.rs b/node/src/components/storage.rs index c3ccf193a7..0c7d4179da 100644 --- a/node/src/components/storage.rs +++ b/node/src/components/storage.rs @@ -80,7 +80,7 @@ pub trait StorageType { fn block_store(&self) -> Arc>; fn deploy_store(&self) -> Arc>; - fn new(config: Config) -> Result + fn new(config: &Config) -> Result where Self: Sized; } @@ -202,7 +202,7 @@ impl StorageType for InMemStorage Arc::clone(&self.block_store) as Arc> } - fn new(_config: Config) -> Result { + fn new(_config: &Config) -> Result { Ok(InMemStorage { block_store: Arc::new(InMemStore::new()), deploy_store: Arc::new(InMemStore::new()), @@ -226,7 +226,7 @@ impl StorageType for LmdbStorage { type Block = B; type Deploy = D; - fn new(config: Config) -> Result { + fn new(config: &Config) -> Result { fs::create_dir_all(&config.path).map_err(|error| Error::CreateDir { dir: config.path.display().to_string(), source: error, diff --git a/node/src/components/storage/config.rs b/node/src/components/storage/config.rs index 01c9378ab6..72493eb4be 100644 --- a/node/src/components/storage/config.rs +++ b/node/src/components/storage/config.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use crate::contract_shared::page_size; use directories::ProjectDirs; -use serde::{de::Deserializer, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use tempfile::TempDir; use tracing::warn; @@ -35,14 +35,14 @@ pub struct Config { /// Defaults to 483,183,820,800 == 450 GiB. /// /// The size should be a multiple of the OS page size. - #[serde(deserialize_with = "deserialize_page_size_multiple")] + #[serde(deserialize_with = "page_size::deserialize_page_size_multiple")] pub max_block_store_size: usize, /// Sets the maximum size of the database to use for the deploy store. /// /// Defaults to 322,122,547,200 == 300 GiB. /// /// The size should be a multiple of the OS page size. - #[serde(deserialize_with = "deserialize_page_size_multiple")] + #[serde(deserialize_with = "page_size::deserialize_page_size_multiple")] pub max_deploy_store_size: usize, } @@ -79,20 +79,3 @@ impl Default for Config { } } } - -/// Deserializes a `usize` but warns if it is not a multiple of the OS page size. -fn deserialize_page_size_multiple<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let value = usize::deserialize(deserializer)?; - let page_size = page_size::get_page_size().unwrap_or(1); - if value % page_size != 0 { - warn!( - "maximum size {} is not multiple of system page size {}", - value, page_size - ); - } - - Ok(value) -} diff --git a/node/src/contract_shared/page_size.rs b/node/src/contract_shared/page_size.rs index ee0f611cf1..8691fe5505 100644 --- a/node/src/contract_shared/page_size.rs +++ b/node/src/contract_shared/page_size.rs @@ -1,9 +1,19 @@ use std::io; +use lazy_static::lazy_static; use libc::{c_long, sysconf, _SC_PAGESIZE}; +use serde::{Deserialize, Deserializer}; +use tracing::warn; + +/// Sensible default for many if not all systems. +const DEFAULT_PAGE_SIZE: usize = 4096; + +lazy_static! { + pub static ref PAGE_SIZE: usize = get_page_size().unwrap_or(DEFAULT_PAGE_SIZE); +} /// Returns OS page size -pub fn get_page_size() -> Result { +fn get_page_size() -> Result { // https://www.gnu.org/software/libc/manual/html_node/Sysconf.html let value: c_long = unsafe { sysconf(_SC_PAGESIZE) }; @@ -13,3 +23,19 @@ pub fn get_page_size() -> Result { Ok(value as usize) } + +/// Deserializes a `usize` but warns if it is not a multiple of the OS page size. +pub fn deserialize_page_size_multiple<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let value = usize::deserialize(deserializer)?; + if value % *PAGE_SIZE != 0 { + warn!( + "maximum size {} is not multiple of system page size {}", + value, *PAGE_SIZE, + ); + } + + Ok(value) +} diff --git a/node/src/contract_storage.rs b/node/src/contract_storage.rs index 7dfb96195b..fe450ba078 100644 --- a/node/src/contract_storage.rs +++ b/node/src/contract_storage.rs @@ -22,7 +22,7 @@ lazy_static! { // page size on x86_64 linux = 4096 bytes // 52428800 / 4096 = 12800 static ref TEST_MAP_SIZE: usize = { - let page_size = crate::contract_shared::page_size::get_page_size().unwrap(); + let page_size = *crate::contract_shared::page_size::PAGE_SIZE; page_size * 12800 }; } diff --git a/node/src/lib.rs b/node/src/lib.rs index f822c02e4e..4a95eb8f27 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -40,6 +40,7 @@ mod utils; pub(crate) use components::small_network::{self, SmallNetwork}; pub use components::{ api_server::Config as ApiServerConfig, + contract_runtime::Config as ContractRuntimeConfig, small_network::{Config as SmallNetworkConfig, Error as SmallNetworkError}, storage::{Config as StorageConfig, Error as StorageError}, }; diff --git a/node/src/reactor/validator.rs b/node/src/reactor/validator.rs index 9b980fbbeb..8d1c8b7966 100644 --- a/node/src/reactor/validator.rs +++ b/node/src/reactor/validator.rs @@ -156,15 +156,20 @@ impl reactor::Reactor for Reactor { span.record("id", &tracing::field::display(net.node_id())); let (pinger, pinger_effects) = Pinger::new(effect_builder); - let storage = Storage::new(cfg.storage)?; + let storage = Storage::new(&cfg.storage)?; let (api_server, api_server_effects) = ApiServer::new(cfg.http_server, effect_builder); let consensus = EraSupervisor::new(); let deploy_gossiper = DeployGossiper::new(cfg.gossip); - let (contract_runtime, _contract_runtime_effects) = ContractRuntime::new(effect_builder); + let (contract_runtime, contract_runtime_effects) = + ContractRuntime::new(&cfg.storage, cfg.contract_runtime, effect_builder); let mut effects = reactor::wrap_effects(Event::Network, net_effects); effects.extend(reactor::wrap_effects(Event::Pinger, pinger_effects)); effects.extend(reactor::wrap_effects(Event::ApiServer, api_server_effects)); + effects.extend(reactor::wrap_effects( + Event::ContractRuntime, + contract_runtime_effects, + )); let rng = ChaCha20Rng::from_entropy(); diff --git a/node/src/reactor/validator/config.rs b/node/src/reactor/validator/config.rs index bd10841dc8..39145ce549 100644 --- a/node/src/reactor/validator/config.rs +++ b/node/src/reactor/validator/config.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - ApiServerConfig, GossipTableConfig, SmallNetworkConfig, StorageConfig, + ApiServerConfig, ContractRuntimeConfig, GossipTableConfig, SmallNetworkConfig, StorageConfig, ROOT_VALIDATOR_LISTENING_PORT, }; @@ -16,6 +16,8 @@ pub struct Config { pub storage: StorageConfig, /// Gossip protocol configuration. pub gossip: GossipTableConfig, + /// Contract runtime configuration. + pub contract_runtime: ContractRuntimeConfig, } impl Default for Config { @@ -25,6 +27,7 @@ impl Default for Config { http_server: ApiServerConfig::default(), storage: StorageConfig::default(), gossip: GossipTableConfig::default(), + contract_runtime: ContractRuntimeConfig::default(), } } } From 721600e67cd3549c484f416982412a57b4652e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Papierski?= Date: Tue, 7 Jul 2020 12:27:37 +0200 Subject: [PATCH 5/8] NDRS-90: Build system improvements This also brings more stable compatibility. --- .drone.yml | 8 +- Cargo.lock | 2 + Makefile | 6 +- grpc/server/Cargo.toml | 2 +- grpc/server/README.md | 2 +- images/CasperLabs_Logo_Horizontal_RGB.png | Bin 0 -> 209476 bytes node/Cargo.toml | 25 +-- node/benches/trie_bench.rs | 27 +-- types/Cargo.toml | 6 + types/benches/bytesrepr_bench.rs | 199 ++++++++++++++-------- types/src/uint.rs | 3 +- 11 files changed, 174 insertions(+), 106 deletions(-) create mode 100644 images/CasperLabs_Logo_Horizontal_RGB.png diff --git a/.drone.yml b/.drone.yml index a3f48ae0ea..80b3af8354 100644 --- a/.drone.yml +++ b/.drone.yml @@ -14,12 +14,12 @@ clone: EMAIL=ci git merge --no-edit FETCH_HEAD fi git rev-parse HEAD - image: "rust:latest" + image: "casperlabs/buildenv:latest" # NOTE: Anchors are per document # Anchor for default buildenv __buildenv: &buildenv - image: "rust:latest" + image: "casperlabs/buildenv:latest" environment: CARGO_HOME: ".cargo" @@ -55,7 +55,7 @@ steps: RUSTFLAGS: '-D warnings' commands: - rustup component add clippy - - cargo clippy --all-targets + - cargo clippy --all-targets --workspace - name: cargo-test <<: *buildenv @@ -86,7 +86,7 @@ trigger: --- # Anchor for default buildenv __buildenv: &buildenv - image: "rust:latest" + image: "casperlabs/buildenv:latest" environment: CARGO_HOME: ".cargo" diff --git a/Cargo.lock b/Cargo.lock index 16aecee149..80e4ad1c11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -392,6 +392,7 @@ dependencies = [ "bytes 0.5.5", "casperlabs-types", "chrono", + "criterion", "derive_more", "directories", "ed25519-dalek", @@ -453,6 +454,7 @@ dependencies = [ "base16", "bitflags 1.2.1", "blake2", + "criterion", "failure", "hex_fmt", "num-derive", diff --git a/Makefile b/Makefile index 980fbc828a..6e7a7114e2 100644 --- a/Makefile +++ b/Makefile @@ -250,9 +250,9 @@ setup-audit: $(CARGO) install cargo-audit .PHONY: setup-rs -setup-rs: rust-toolchain - $(RUSTUP) update - $(RUSTUP) toolchain install $(RUST_TOOLCHAIN) +setup-rs: + $(RUSTUP) update --no-self-update + $(RUSTUP) toolchain install --no-self-update $(RUST_TOOLCHAIN) $(RUSTUP) target add --toolchain $(RUST_TOOLCHAIN) wasm32-unknown-unknown .PHONY: setup-stable-rs diff --git a/grpc/server/Cargo.toml b/grpc/server/Cargo.toml index caf83b6335..3324116927 100644 --- a/grpc/server/Cargo.toml +++ b/grpc/server/Cargo.toml @@ -25,7 +25,6 @@ dirs = "2" grpc = "0.6.1" lmdb = "0.8" log = "0.4.8" -proptest = "0.9.4" protobuf = "=2.8" types = { version = "0.6.0", path = "../../types", package = "casperlabs-types", features = ["std", "gens"] } @@ -37,6 +36,7 @@ protoc-rust-grpc = "0.6.1" [dev-dependencies] parity-wasm = "0.41.0" rand = "0.7.2" +proptest = "0.9.4" [features] test-support = ["node/test-support"] diff --git a/grpc/server/README.md b/grpc/server/README.md index c765ed89f9..58edd21a8c 100644 --- a/grpc/server/README.md +++ b/grpc/server/README.md @@ -1,6 +1,6 @@ # `casperlabs-engine-grpc-server` -[![LOGO](https://raw.githubusercontent.com/CasperLabs/CasperLabs/master/CasperLabs_Logo_Horizontal_RGB.png)](https://casperlabs.io/) +[![LOGO](../../images/CasperLabs_Logo_Horizontal_RGB.png)](https://casperlabs.io/) [![Build Status](https://drone-auto.casperlabs.io/api/badges/CasperLabs/CasperLabs/status.svg?branch=dev)](http://drone-auto.casperlabs.io/CasperLabs/CasperLabs) [![Crates.io](https://img.shields.io/crates/v/casperlabs-engine-grpc-server)](https://crates.io/crates/casperlabs-engine-grpc-server) diff --git a/images/CasperLabs_Logo_Horizontal_RGB.png b/images/CasperLabs_Logo_Horizontal_RGB.png new file mode 100644 index 0000000000000000000000000000000000000000..ebe5087c3194ada20930def4f7849e9e76034272 GIT binary patch literal 209476 zcmafc2|SeR+yAJ9M22(978!>&Yu2$1*~(tZQrU7Md-f5=XklbKg(NbhEG?F7*%eX9 z5VG$yBxdY`!T)~r{?7mXocBEap3mo;^URq0zOVaQzt?pyC&I)?mu(;KJ_G{6cJ_?c zc?3dG9f4r7W!?+Ex#)V+1^kEQ#u-a*1VTa>`iBAg9OH*T96_Ab(zt+4`7ucHyO9L?o+o#nlQWI_etj`E&1kE!!dH`Uv$D<~_PnK6`HYRmpU*JW^q~a%$JWcQ2Q) zl4}uT7}<3d|=e05a8m^^AzOzctpmY}1Ql zQz=zn1Qd{-s`c%8VIgyZ=7#-)Y9p(^n2w4vgAW)+^!0=+-&4>{x7tlyK4adRJK?F# zVC2(09cm>k6$pF;mpT&yVS66XU{5CACU0;BMi5a~YoN$s@+}PCve%;}1;oR$!4^)<6&wxUtX7yM4ag|pQzUDRLzN=9A+hj6O}k!O z>x#Ua|K;`>$PQqhty?>4#%JoK6hkN^D0<$Fu81UD6V&wRn=4-shiL`APIKO2NoDzi z4iu_Y`0DU_#?m6kX`3#!J@Ab>&mh3;@#g2e9&6WIYKh$;l{PdA-N9LIaKJY*;o4z} zb!QbdV7sSq#jHGnu$Vdfu=v0)v_p;q(yS3}W}@+V!P6L*zPUFmLhxhQHtk3X>nD;0 z6j&li%~Eh~WX45uE&!M70w^>b?7U1jJ-S~NKcA3|C0x_uw~Wc7Id=Gw_rlK!KP$XrjJFd~ zT2W79FHtO`nP|TuAy!2FVx)e}9ZqZ=wpca_Pn@mtPd;1}$pY%F zRiFACd&?MEaJ_G?i2&b9%6&&!gU)?&I*4p0C>C&#CL~a;$t4w1$KYE-oD=F?sNmb759N1^;@T{sh}i;$7*?9YmE*2#rOYWpz$M+m`utZ zbl7RVLS4;U5%0-1MtTku4ZfCvAE?%0woedPyfwsgXhzKZTJ>EV6TraW9{C+ZPdhJ? z6-mHSwxT}bv|i~MHO9cJ5F&X;g-p^Pbeuxrd;)c@OQO``Qw~Bw3hqfR0I}8(%-1C1 zK?mTQ@Yu)Cy}Ka3O`E{A0X9^=V-qK&>q+KF&yjDg_lb{7E-P}wx2R0Dx(vuLAkxs6 ze;9spn#RNbmU?ricTppH3JZq@Jd5z=e?YR3vKu2C5SV`vw<T` zDfcQ*qQ&|7;YLRwQg_U%Rx3UQsBT2y`J`2mEK}WIaEiR68Bz=)S&Ibh_`?g%mR?Fe zLGS}$1(au&`{Kb5F29;6A`n*H&rHstuKrgSYdU)ggdy9&gcNq+vG*)OE2yg%y&fn1 zLT6KTc37#;mQL>C!0kn`qsHFUcIUU3vnPCzNG5kC zo2A?dA6H)Z2H2D*%QxMCBH_i)dtK714^+6FR~|_S= zjEwqyohdE@KhSY!Rt&ju6dRvRP;~8^`}zxkQ{5`$!4$bmcf7z876CU5>ftwd$AJ$= zLg;|IMs1*yY()LzC{REycplZ3&c5n)0X(_z6KP|_+O}`5+`X6^-so5gaB9GIZ8OiU z&)f3SDv=e0M+zW5{DLJk7f7(bA44Breb+rYk9Hpl84PSN5Al}#YY4M0wO*{QqyliJ zd{AoYV&T5xjWVZ-jy_#@j^Xp5$vk@Wz*JBkBe09SIc~crk2=nwMG<@n;;$|>#db;0 zs2Nz!dFqcXU<6IC;5(yUgNVC4XHBKcqGC!Wx(1enmPiLQd}xIaOt;InY=sblIHnkv}0CHn)AyI9GFQV&|wn)}Efj07% z`ojHjNpkWp{O6WFa5}biVbrx=YL9m;Qw7iIsb=wjYN{asuRlWmJdX9<2KnFJ-e1t6 z0I`J+6}06FxMA7hm&g^Mdf~-{4?j|HKu~^60`(1Kevd)WhkHlsB7-rggW zj**Q^1d-bk=+|L*S=X1(o8L7o4M&n%et|`5dO-@Bi9ldQG}AVKb8y|din|8^``n}J z>yOePz&cbR?%HXk)dFyu?=Nx~=hDj?fX=-130V+&gvV~v)*=JBkmev>MAR5%e%Sb3 z-a``AIsnfP1PbyxJP3rf$){;o0w1M!ttz6_06`7XGb)5o)leic zwpBxm4)E;DU}l{U8_0&N?X9XChv0;aKsc8GTB^VZsT!ZAr2xXZI-HuZ>MdC6);CwO zBGi-epucyH;DkagnS5Eq)&E zlBOhYcy^oc0oaFU;=ts^Fe^!3v$O0`ViL(JEBr+ZC_%@2fEEuy3k}5Kl8&T`|Mv4d z@bgq=C6WVeH4$!bYhr#ZpcnZAr;V(f$1@)c)4yjJnL(ho3cT^fE2(rLKrN=p|72fjI z#i0SW(k6<2!;mE*9f_(F#tzr6^h=@-mkYyA4zW|Jc$+oha}ep_`riBs^T+~omd@?7 zR7-fDm$nbGiQAC%RTeT0=O02Qiex3FV+jvBR8T9?jBz`O9?Y0OS%YK!ZB=?k|1wp5 zV@NWH>D`}?VnBZbs_=q)qy))%cu9Ij^)mG^FdOpTafX=aJ#k|)@Up9&fKr1lKGav; zVoGfW;LG91F&>K290vDxvTGO`~Gc_^5ww4adr4)d;7OFF5GDx#=H=kX-z)#Bk#qBRyF$0x9Y>}F_TVAGe_P#} zz-+Bo_Y>ZhZ_j{8DnDNdD^A;f`p8v;x*-S-1Vw)z>$X#<7&ZefeqILA*V|F7X>~Gu zIBN{Qz~ZROHibQP-1r1Kc&pKPb=UM}_r>ib_r6v#Y&L@beixc!x*D#UOsz}XCe~DcC4DE}{;pfsT{_?IUC#G(>Ltkriuew&>~Jmw zQRy&{^G*b)DVNV1$#kkHOP?|GTEJYEauZB0QTDT^sew=_O#gVgSHWlBGjPe-CMc63)a$B%5l9eE>cL z=6Uqk>JTI1gy&pa<(th19q*^QIBUX{_{(MJ=AWjECVY61+lP>plLTTTm3LUXK5x^7 zqQL#m8&>D>M>pQxLDY5vG_5#&U$c-Q7#re5I}w4>JiJa?Z+e|$302D?35t<~%_g$q zw4g{<@~C6&wGO{-VXQR_kpL79`Wfy;To`G&-}@ItChg+mZZ*V(tKZr>UrfkKqL=Se z)Toc}DQ)kmm_@+C2e@B32);?n^c&CutviAvJjUFoBB;4LVQ-1Y5xrQC% zND1Hy>Pm*H$X*Nn@GkQ*RBU`yE_2ACZsmi-3;;o|f_GiF& zEYu_IRz)aNoItT}QJK#<)3tb!BfH-ZQdX0!B9A3qJg(R{f5UmVEW%5@zT(Wz&Aa_6KT%EJ&%Z1%Ga8_oc+C> zNvN@ILQi4K>;4GB_Q#wmZa>pCvA>)B^Q8x>`P=$=ImhZLax~~5s@R!|4jqKt+d3voe#JO!eh#QA&4{gKg!w{o9^0d>5thweUbfD^6{n|Rf+jUg@cNh z0+I7BMzc<{xBp4N-=9Y&p}^|>ht%gsWoiuHu63&=JxJ*rOzB%B(72Jx4;5Ghy#QYa zyVRKfZu8F<>lomsrB0%YnNG(9~uFnxstC&EJy{J`L@buG7Q`A(d!r#CD=L^;k66pYQnhayEDSoY|SieCc z%MZI^p;aGq&2T8c&c@q0_E$V4L22-#=#)Mo_4NjwB)q2nu19Pnfv8I?*S5oHYd?a3 zmgoFp`9?($lBAK=^Q3#F@{9U_HqO;tZ z0q+=R8h8R{eV3vI@4B(u_ph#8>sVGWYxZu=Ih=Z1a{pt}e&o`qT9GV#T|V_R zqydq^$$#GR6KAuPA9^T5?f=J;$5pq>}&q8vp4bT7a z?y`(_U;ncg;BUfXcU;$pRKrsmOb_=;xhNCwwub;gTiu$9Tqt)g$BmvJVL6z3d+ktI zV4JqltR0|zFYHQJt$pRCyzaew5eAolGrYf5A%04u+pwE*d7yHlb6uDf`$8zHy3#*P zYW<7snhd(w5%`fCSiG@ew;zJ3~@4tNv7*QGT z)hUsv>SK0G3Gcd2X04o|r|8mvb@lpeCZ)sreX2!v3m|dmUT5ErITLcmo{ZP$!3{0^| zThz6C0fA)*oTMXlNI8eCffyEsc-~Lxv+@vA`fdYQu)$Arrg%FaGVHeD7WpSxe}CTI zNoZWVIb?oMOG!-bV?r1-S#fU;4u(tEExi;9vwlAn2^eTOYxcyPURY>JMi*}wb_Y7Z z3S=bm=Y(AXFn{cqI_J^EGv_Ec7E(8r2!wmWZs|E;VX-+!ReS?)*b_k*eXJyH_%|{C ze5u}9bVlIAZ(cSUV9gk27h3W%^QYWxR}jwf+jjq&uK^|~`8d~x`@}z+{{1=R=W0jc z!PCX+*ZkC2GqwRHDBsh5Xwp}tg#HJai{4^TXQ{>C^ZVaT{`vB=3k05=Z{8|sx0KR1 zdW|_llJW%Te^KbRjnQm7*)E_bO#yg2!)_uEJT1h)V(UCMM#Q6{l)k%Ce{ikXcPwWe zRFp&)Z?)~hR{no>cd0a{^j(bjSm#|9P1pqLYyj&}@Eg|206*3?ut~Qcwr9k~D@;~K z5)RAj!Ie$eng?=kUb;I3BO4lTCme1|RI%;mOWac`FJJER@C% zLm?31I&>_f9PxVvYer24p9V{Y4V(0m*RXZG)OyM8HcfhvV>2;S>4{E#!60lzkir5f z>QbdaeG#}P!o_O2)1u!SGiQv&J#fJ16fjCz{g19g!GW~dG0{2qFeD41)W1M6?9MZQ zEl}gTyNnTtN4FCYN?axRmA&OLgsQ%91TZ3sstt}XJU&sX$y}AvHMqeR-X8mb)caO5e@E8&q{D@|*{QK_cNmHX|MCaYxbv?to|>KM(QTN@5_<2}7y zSB!OsukZOn8uo1@Qs^vq1)$-f?BzK_gzt_(l288>$bxDC2m_-S!EQAvo$;Se;BR97 z_Y2;9X1RP_2%vM|{kf4fmxN6_eKt=3)fzvGp5FuM@Gwew1^g`9*lP+%!b zT{GCKqRCMRq#gu*S-bwV+bN-^n+eBOvu66pU^l`8NR$5kQGdRpu$BOCj@^9Sti3ZA zr=wXbwcr1}0r>Viw94KRktS>Urs(yH6p*t&3f+FAm3BV-AZ)qRoF?f&oBM_s@Vy`}+f_$# zjlaK9J-zO&{{kQc4$ZB=S#NwqxqCToyFhT$ntL<ReDG{h1@yxq3wYFkznk0 z!(E1`o*Q}4Dg?}vO@K5{;r*AW!HXTsCojMF%5e^7v%z6|t5sp`e)DSWcjNa{{?bdg z106|>yQ6AHGbwjrx&nI?ckJq z;W2w4AeObu!0DT=y#+u%93URaEBjqP)+kujoHBr$JzfclOogDn5JgZZY5quW1Bmze zU#80S@%}Fa_e2e6WzNKs^RrU=D!A9}Se{V=&2|Y)1Jykg1{>{6!n;@YBU(B+lD4jw zMYY_$Ym%Z{A6SQPS0*;!+cC@=TgRD}E*R;f%3p)TNIH1oRT%#4cdbb@`GKlXxg@p5fPv~MI zYD}5W;g44FTgsaIFdEZE56m)R~($l1Mt7=ube=WY2Raq8xYZ?9~ zB%H5oKWjHr8(n@pp@2vJ;%`@ z744pZj~?NW$@ptx*E7TmubnOriU|BIisZI7IB`dS(_81>ij&w}H9D3fN!zdgmo)tL zNJ?h;)l9D^w8KufW%wo7+w&Pw`zqnaH@#zV;%{Bcx7tS5K;@=R)tQuLJA(sXbn-S( z{^QI_R?2EwX(b>kY5byB7v;T(Z@JU!0Ll~+XtuqTQb;QS=eK5Rj}N+bxoM?XzLVWg z@<$dNBOgE)0}oWkc}8%kCMOua`iqENcCNDK<3&h;LR|&2;2z;{|F^qW9+=uFM9wS@PRP<_sx1ISZh6Ng$X7rTX?^?qf2`ZXL_4&9Ie- zM;L%7(v;A}n(+%1d6LXq5Cey8*LX#qZ5+_NbI21^2-43j@;AwltvEL4s-%=mp>;Tz z$AksyOz&sCVx?C~tBz)*V!Da{`}C&5{_no^`N4sde`IJng~KhAGtP;ZJy3GK3*0d81J%Q zp8S>aoA3>L7K3;!Y!yUZ8=Cib;N+qRg3iR<#$Fe8bx~a(=VD?BHU`}CV@1G`oO^RE z_3)z;-4bw=?DT@b9oSD~*}r&ZI1#H>d1svPRt_xx)Wn0;M0wHy1CToXy7^W&2Y0pn z0f7V$jv}4YrG&(09YQV_EaBg&qUiEuol@V^5n8vdwS0u;O*s z${@+uYiT`8`mcOYGezYLC$kcf7{{5WW&c09sJ7<7@VO7!fAOgz$eI@a0?^-t00%t+ z*rM=QT<;B%XUFo>SXZkuc1hP=TVLG3b_*_HUngmU7GDsfAHstSXfHQj@_PA}#hxc) zz_b~&c``EKU|%yQqqoaFHRlM`0j^`M^I+{g4zkwC07 z2V2=#kv^hV?aoCFfc!w=A#9mqCmqA*fhf7gL5d-`c&9wmpZ9LiqVvl;5YmV|N4aj; zosCLyfvqX0org-mgS%S{jAk{oemwLx!If`%rC#-<)PPtD)FIl1372SEad`~y5&h>W z2y_&!2uTTyk*Q*8z!$p~MO`$Sr7HAc#0bcZ%<1*#jIh46Qx|dA_Nkuh8kErG1xnJn zz`PAvGV=7et@xvzCp+0SW)I}z&s9VdG5|m?K5%QE0~7AJ91OgTPKyQjdkChg;E2vt z`Sf~lBzET2;y+3 z8gm0s0qZ-=N*M`1tKb~MOWqyJM-Rf<4wR$g@2`tt)l!~BP`=+}PEYg}QfnCiCGv;N z>&ndn(Sks~z>d49NE*5_dnYiW?M+kpwuv94PR}L7=>_6P(1N&u_}r3 z%^CE~U7p5UcoCNu&aoaN?(zAWR?6FMDR}?{S2yzx?w0)KhizR6uH}aK!&gw^`vw*f6i}@opJ2tE)0|oXF{SVx&OsEF<;Q$a z;>6ZmxEoKZ@&ORW3K$yx$XZcEsa@J9yl|U*9d0i8a**@5YDA=_UJy{-elWv%uljm9 zPOdeU*%+*)&v=kSf%>Fs=frn8<`rk^&}^m~%q_|8!Gp2t3v z(PPC5DUE&B7+2=74m~%($ZR-t;rbSe_jT>{hlM(2p*oJc516${#sqgB>VFf(ap6ha z+J-jATV?*QIh2yt5$bCaC%cq|oPU2lySCpw zB}NREcH3UyKm;s#*9SS3)Hsx@N@J0pm#$8AM_2EqCCD!IqdTo|1ANt1)y644<33;4 zS?#LpN$EhQPR-t9~?0*Wb+6=3NITEgXbg+cw-&%wRn~0uNMYH5AN`UfO6j)JjQop})DcSo;KYZn{wDFp1V=`_cx= zR!5s{cc=P~`#4bH^cew8=hkjMPal>TUDULHrNeHX3ItTXq#-5gH07_)th)U}?0S`8 zQ}9Y9U)IdN*4_T1ICjG(qKLn$ceV!aI(>g{>He=%_6kN7dtvc)?NPv#)xW~F6vIoj z4PPc?d};*J77ONRF^)Jv=2(AcU_Z6Cu6nY4@g*3+#?4`Vk$!g`b3&2nl|GLBwN_hi z5s*BA_6cQ?K^|0m!lMW~9U=%pL3cURm7@tqzkR(^Dso4{_LEq`#|4cwr?u*(-1?{5 zB&VU4F|q=3z6Lc`BYd?6Fs?jks=&}FaUZ4=M&{f!r?;az6#FMHP-7#EMmO2 z*N2hwHVIWzS?|d$pHs%k#Q5$<7%tV`scG9_Yao2rA=mS~>p{b+MSBF=Av@Uni%~W| zXd$*Nc-O(!?6dTsgQ?o}7kSrjKRlx`KpTknH8Y*2ylIPf*IM-0JGNBAXFJ+FB!jsT zK^3w~6*7@T45eXuOn>{df)To5BUmfQVz}UX=L~_C*O0g>%C@P&NVZJ}ws4PNxUn10KbwQYKON?dud?7rdmhUsTl%&C7ZEN;|m!4E17 z=2`kIozzZJklB~C(3l#}yndfY+7W%;^&OQ8;O$ct<8LFa^NaoEo=VXK{xBTbO($Zy zy2X^sPzhk>5CWy4GpfuJM7L^-7m#p}#c|FaiB zlPLTy{9C~>w8I+U+w>PE2A2}Iks<>+y&Lck%;g9wRi{2&S4nueIdqX<4YPK8_0u`6 zN)K%8n)kS6*_PdGxn~pUNw+9zcK`?rWp?nQ71=daI|jT-B;h5Gw!zv>$uDg-%$MHx z{os*%4S>8oy0t&rw7Qz8TmM=3N#goM_F4zOB1xK#K@Vz--=_x+ZnS(9eWF#b%QwO| z>9}-X2!1HXJ#C@d`aq`AXcu~{* zMnz?tdrGeG)h^lw%4R$3_mta=C%W502N$DbngUs18Cnf+wZ$vhC9wNMhn{lTt8L*F z+h%Gy;zlxUt^1Q*mwSp4*gdvvzlnCOZ(P{w1*$?0TxbW z|CpOz4xSr?9Y_qoyZkdE7PO|7wJ84jnlqX;r}z=4>Gb4yj&*P};SkcJd){&3x_daO zW_r_fS1VI=-U;2dSl}dCJEJ?!YE_m7R(}a6C`!x1jxnbPCX>R6!4w5lAJJ_K;Vj;x zPN&-ehu;-#ip$-SLxEcO=WfKq=Lk1=SXiry>^QGbJ#9_0o3XE)Z7(Ku< z@0NGIl*pe|Z~W(uHIgLRVAl!pd7w%x7Dhv_{&9;vUww=mJ7dbM+E~2UtLK`mR2fa6 zRCr#39C6s{d#5#-%YH()T1Dg2mc8r9#+6qZH(}2r;nzSKe^7&-;sJGhWQ;tMKzeB3 zk1n@5-J?*P>f4~EQ-1(C-wiO3-h}|S1tJfU!Ye>A1Cck7>D9%uni@IZEeIkR?T1|) z!Eb;-?qEz$VM2j15M$)zxQu?0J5|Y~!$`_yMgKZ?J=rxU5Jv&K06wUCml)^2ojw{c zHxU@ltdwjr_hc@KZdRQokh~98z^h=lPYXyJ4;bt@Y}-&zn)yq3U~OPf@il6!fh&2-VqN&<-w&@|Q!qOEd&=%Dx3KxeUMoikMU1Ro*9p9o$ghea zVTv1XbhJ?tTnaMDU%PK6k1x0#PC`E#EsWYIJ3j9{Yzgm9MS*V-d!CMoA()pm3nSw* zj)MTg>|OIzhBeLfiR@CM%)W+Jwd$agVb%8tj=%Hg$P4Q4LF&r z2DyTBB>#)B92AH`C`l=Uj;V{$pBFxQS-6)LryXm)V29ZBRWND<6T>|W+Y&>uJD!T} zZ6slivTc4h;|P`z+Q{=Xz14U~1UnQ&^Ajab6G7xPdxy5*MlOBe#K3CBhz2m2!tz1a)`*u%3ntbsFI|qiC4nQPWnY%jma)IO>i3kBF_|c3cjH4)W3Bt@5 zZm=KJ*%RzpPlDwf_6FdbYkbdSLM@+*sJ#C2p0?4&dQxJ>9sDB6J>n^OSUGzCh~`?< z#&ImJH@d@@k$fqV=StSodefF;}#bj@l{?nu-OD&7p!F$&AqylmA)749B`Eju?!BepW8UNirIla7sruH zbs$9p(d;e=<3WNg!L5O5Hw71R!6Ynx)vqMz7cC+d?BIRD^(e|n zjM=uZo@7@I;7IrCOoPn`7^~`kx*d!*WuM<3Q8jrDfl}I97r;4i1lO(uqZ3y;9_p0c z8!5YVlJ6|A8WTApTu1PcU2_U3cBv8!w~Bm5c|W6c_?ca|3U0OVD8f>Y{= zLri?DN~jfx8#GvhK=eofZpdZH>uCCYX(L*2(_3@zQ!M}-4>^is_xIvJ3i1kc0r<_K zk+m1FfFcQK4rH~g2oenJi|+6Qaho-87M5(NfOuVp zE>$>tjC_Mh6bS6RXkKyAOzR3D_$B9Q5?E1hD)zItBqlg6R#9O${2;`^z7OZ4 z+@Di^JWF3q-AmIMBiCBRD=t=qg(nD4k)5VWPb#tonJE~VPhRSeCJ3U&GR|(Q+)f0j zNhKLje^L>;7UtidAUUAfmz<`|!Qn*BmX-p7NbP0>aktHM_7#`ceGvp!qF{~{?jev; zAF%TD5J9Ae@R)-}WX18kHID$G>`$=Ou*_%!hyBtVj+WHzcT3X3@g<+aIY2)HFbpQk=R0;3li9*>WM|A#`ca|fqdK`4}F>D$8qW>m6FsuDI zPHgpocL98;`ZgyizyHGb`m2u$o^USQ^(~Wix&%rvK~Ob8AG?-0YA2AHj>#S2zafd{ z1_d!-=M_M?A@WN_PFTN;os3$0H9T1e;K(01_zDdtJxhFd@mbB9L&%MTbuE)0x06H) zMNc=&f)I^8hQ4LJYfOCD)cSdP&0NDL@|nk9qdP9HsVyxAqM zO?Q0tp@P#?UQo=RMoo+JPwDi_P=!&GbZ%*Oq0oQE=F@hiZ}_>&nq|_O%#`oi+8Qxf z;%;L?*j8!xoZu%?>B*UYS2%D_2(-cG;&F{107s&=La zHU{9>o_=nzViB@g8``rBs57q~e%&O5*=*h3{@N{|!nQfPy;egz;VOXbzOR6r5JKP~LdhiAp3lzCYhtzj3jl&UqvF%hb!w-!yE>NC+yo9+jS^MG{o@+bT|t2PkcR{PnSQZI-9RK-hDs zwWR<*ytrQ%4EDAOivg^xer{K!_dO!{*!(p{$8@)soC_172_u_XYbCrIB(j<$g$qFw z1tRjdc zkp(dvd-O--5+}CG3G2AwG(`gTi(qSN2PjS#d=ehp)~<(cT9LwSp-HH*i&}IUw#{q2 z>*E0BEgyz;Lf^qHMx9!F9zS0gVmdf$Mh{}YuRZU5-2f1|R_>;93->5&tLT(=j*;N}6{FQ@*yp3immcGI_Db=0I@tsxL z98vuTu%I3C%#rh^TKp0|w&;~DP^s|+RX9NN?eBhM zfhQv)=PT!*SI(Dl;)k0;DS%ighUTHJHvWZsSQB1JAcO#y0V3s#5!elACTxJx@h@#2 zy7e;2wxf4WglZ!(OwItf#?)222caD&1z2m}5AGvw%Ya;Hp?9NTq9&Ps98|P9U{~Ae z;FwExXf_A1JXU~+I$!aDn?kVt)CUVya*db$LHt1QQIn`>|5C|2u%4<0WYNw?==+hE zou;fBrICw0Fe(3RmqbuwJXsFZcfQ ziEg^axTNsULKETu=+jecg4`jqI6ydxoR5`VI?5UZ3o6CHe$afU4S7Tj*m&6j>NYCO z3`_fb#^Wj1NPHnY9$IY-eGkKM$`GNIk3Tv7___(K>`f2_4!Prdf%}mcWvgnFp{LI5 zdc-*IZR-w-r;!9f-Vn%ooY!NLfY;k5w>957Cl&pm`9!hC2`;_#_T5jcj?L*VS|mky@AN+>|QF8du5aw={TK(CN&u-dbbG;3dH+O+DjipK9t z!1`^uR$apwHU$R8g>Cu3Gd0Z(($ODDDs-W4rncXJh$8g%Y#u?m^WxS(1gsclxBy(9 zXN+Sck@TLG1iQ=V5~Pq#O%tOB+!7v1sV@Vmhyc9pwRhN!UElQ(p6z785AzkNlWXX} zX-WW`h_BxVI(WQ7^ngu~ql!xY>fRUjGXVcp)(W%DwMpjX&x+vyIyroN2!^6EBt~O08A{vYD-`yJ7N}i_qTE2;-)rg zHZaStN7#b5_-|hS8VL%bFmqAof+*lv4aJmt=%^cl0@Pe4&>Dh;oPY*tb$xMPD}$8& z7#SE3Ob2bdK#bw?Gdzsw11_BE3$)}I&=P?`9Lv%!Djp=c_lTww6mxPIoIg;1y+eX} zxbhiI$|VMQBRc;G4y4ghgq_L==Xb^t2H;=s5e_+g5(iiz7z5W+8gw_038**NW`K!Q z8%CrF%BeLR66s;BRRE>Ga~^!UUL%3Z0GwdT;;X+y)|3CG!h2-wT#Y8ycIiV&+SR`ot4g2O4sf&A55 za4u2hv6u0B8}-gzONQ#xyz3b}!6go=USiwzui!|_4uv*8ox0K|6QV=s$9=?y59K6T4fU#(VExV(cCWYVV_@ejrvnh!gBL(I-xI=c-8a8w0EOh#sBxlc zt0I>fX{Kw7%MYbJjX_m9*aI#(vCV+kkG=XG5lOZ*LC$+;ry|x{TK>3&vq%+0&~Tp=%vLHqV1(ijKwTwhj6-xC40|LSi1X9I(Z(Z5C+2 zqKM-_V2FGZuy-^W2whuC#UqFfikP^`7DcT3QGlu0;I=>E4WZ4Qct%in&ejAq8XW!2 zyl+b8Ple$FJ`WTMdv+bTu73e;*8v{5IVSnhs+`@m-#|Ln`J68+E(qg-6-T$;1U^X& z+UY@1oEIHIssQ<%vVh~mSFooAfj9`#sx+fX+)D1~G6+=xyz8_3T6g8M9G$dgjvZ~e z0H+xi3ScuK!h(6;8~=S0oS+HoF5*HSLQU0vd6sVTdCFVfw-4BrE9}|)S34H~i{WW# z^-bUvVhH^0n?2O>RIPeT0KF`-xH=rzi~z>vgIzM4fliDpm*?}12mv+}Wq=YDYAkcK zSoI`H0N<;xNu=~{$baT_)vhmq-JYH=0mbYqNlJ)UjbP&eMo8) zDjH}?e%UAXE=Rmjrx6r@*x*WlcwZn^d2lV2?Pl)hpa9?>TaV$kgi!1t1Cr^Ej@uBq zcnrBjj`kP_03`#sU+S?YRrf|8%Z%4xSrFE8zJeYR-+i4FRE|)x?jDzJI3yF$g~nBL zW;(nxdst%t$}69b+yWU%)vA<~`RPvA_jbJNR&h{WYh=67fAy{J6(@WcCQ#|9GVlJq z$KEL1_}FSBdXw>~`4MO6UpIH#^FF%LxzFHr?FF->oF*nwWBGPM@1Okq{yFI@Q?Xw+ zb4-_2YVY>3`M+%S|4td2__sZhkS^HrX<<+CYdvJ-vXv}!d0bZY6F#(KCWFe^#%i9k8YpSrzI z4Mg?Nhb$;-{%n%8bDxlY=0Kx<{yzeyukLDOpeDwrfD4-(t~!WG>l4+obQ0*6FgOEhZ*Fl-!_qsylCzt5}SdY(b6ZQ_fZ+xcNx{ zvmD?lPS^w(3DJB(cxqf`Br%MMS~=mC5l;oZJ8J{zd9^pY1hxMk;_2%PpC-_0AA zUN`$_g(KRZrroTXq2UU_sgyv`5A|S^e3>B}(`>sy^l$?pF}6aEPYv9EFX3k`>==9S z)T`BuDc`;R;4lx;vv;}fY3fOU*%UW`CU>)hd<8S2SyNw)j&{m*l53Zt;k=Cu2rNp# z55uUA?cr*$@T_2Ads5HHU7c!|2b5QyZz!j|fAtnw*Pc0L+vl?8lpJAbxo$GW5m?F0 z;?U+wh)#V7aU^@DYVHIow{hc%t!&6Kw3SwUTv(RpA!N7}X24*?0-^R|;==G{e?Y)` zmcJ$p0SGflcOO46jb7mIVW4f(dzR~x3dH)kM^yzdv59K2Oy%G#Szbu%@mz5n`%7~# zC02wLR4fx*L~&Olz^*K z&EB%rx+Uv}XKYor>LB9|=TF|pWkxcFqRhWPX5W)HHUhaAORhQynOi)siQFZr|0yR- z&DfD+>`8fr^!}js_sc!PL0i1FtscAINB=eUC&vt zSQ&m2{UmC&&*lRO1MXG_cdaJ#dWoZFjj7z0p}B(JeW^wBmetwaOM7B=r#)_2-9y_$ zJ`TfY#L4B)o10Y;yDrP@owWu^Tw3G5N&Tw$&AyTZPW-?7;@JLIH_}tS!M^k~*ii3K z*_|34>LKuL5=*L(E?vt>-ymRzsP9!Y@n?^r*N{)lf=WMA-oOpk0+yZg!G-^u1lpiP zt>BQ)=8fyWEgor5X1&ak9!acA1pg-m?fSccvwC!HB-sj6`Q+UttnsHDYhh#F)CfnG z{EOoo4S9Z{Iy;v38P3bv{$4GoZJS(j?mJe!J4kNV|==SvM3pzmG^F>71SdTj~Sk@K?Obx$DTWQLdBUH_3pMu(nvo zl9K13_N~B18c9oWUUv@}LzJFWX#s1>=Rk2{oE3(!xSQ{y z*Ug#D+YR)(4cM)oFR8DO@9LM|d)~us537IK5<^j@j+Q=@)K$>d##C|`dLMN3a>s3+ z0JC%gf_-3GrGw)X9DLh2zB%V(H>6l*$xz0{ND#bAWniJmBC$_jK1GV?tw7gm$?i* z1gvzNt(*QeIeq*3AT|%zM>~b{lGw%P z^=F$bbo}JsAAABpt)g~O)WJTsgWtb27~6cB7DM6qcIQ(aV;iNY(}@NkUsD5@!e7Zd zy4rvC#pBNP+Xh)sSsN2$N!Ecr{ieAlx$2AB~DUS!dRSvah_;wcYL^J@p?)_h%e2}o${uzaw z>h`;H7hO8qVd~Whh7KS) z^@GcUoUn`_Q&O5ul89C+^8D8^z?VVbrW9Lei66|Y=XbwPln=V3XLQt_uxdM9Xe;8} zww5`uR2MZy?#iK|Uz&I+3FNVPwwI`$L|ZYNl;+HYmm)nOoLUY z+ik1pd9#smum~RUz%+PowbbWcG`H~7tpYX>m6T9W%?!gsk%a3H@JR&l>0ayXK33|B znQ%V7s$gK3(l}iHl4Y$Ufmp~Xl66x+RpE2*3sT3-gO1TJOzlslZ{fbsPNl|D{1G8T)>4L|3}z+$5Y+@f8a-1 zmC-37LLGNWcJ?N#vMPn_xJ$D4K31WShN6t4VH6qJ>o_8^MfN!MNE|{ozt?epzK`Gc z@%???_wW35KRmjx_jSGI^EIy5^)Q)C`$iw4qu+5Mc#H%h9>}@2=QsHx=BCU6sl|O1 zm937fnBN#_pmx;V6VS1w)|WE-y-;yGLErq4A(`k!uUwTtAs%eY<#3$POIn|5K&19tvb|+Xjsg4XkR%<2 zYJ>J{iw4!VuikY-MA6$j$GSfgylzg-O7(u|iU8WWE-tD0vb-6BE+nHLE7j2LYU6ca zWvk3>2MJK1P;xK_Q1~W57S$R}*_tNWO~*_1GsV<~b%)O^*o;nPs@8v~eU5uIISTZg zOJAhjs9Iw{!ecP$WXDGLelmb~eRkspez=Un}ni8AeJeVI*uP;$YRbV67feKTC8Q(%?NE413KS}_1KGVo=#(P*|E7~8=8F*+j8z9Zb`aTQ|D7yJ`0GV zkH4b5-<%L2O~e%ypw@8rLN_D8N!y~kKcayxJ>|{E$EI{&el}Z!^mS&F*RH(qRXjnh z-iDamC#oQd1&a*G9*{ax1O#09n@j-dTw}5H(9Kr+0Z3L=hk!bsKb>}T)tu9U2O~(I zsSV&;t)?+F_DX9dAM$JmRA4mZ^l?M<3nf9n zm1s`$OliL>#io-?v)RoqL?xU51CbEVj(Se+;!9QkGyjV%%BypwI;FWUQUtqWdBFC; zF)xS(KPZ2`z+$Dv%K7tV{Y=k~*Ed$@wX5sMbu6&p8Uw6!^ks#J1rD+WPv+e-9inCG z`|qWjn|P_zw2mm>+uEx6X83{(P|%^3KQ9E9YLBhvy$dh4_d33^<1Pa6da4H4(TW0J zq^<9*4Q2@jh1km0)g-!apL|&e&(~Oi*RceiT_?}dPl`WhZle)cYHCGUfsY;(FORGP z3Y|n+6GiX^AF7`y#@S;vWES-X+hs>bdUWGUEZkb=7znxff-PA=qFDk0a1OLal+YL` zts5jQwCbWNl5ZJEAhlTpW<&3@7zks$NHBw*>(szN2t>PJ{W- z!Z-D&-;U?oCfl=dPwTRq*Xo|;Y1JHN9COq~+;9YW-@wGQpe0?>jP4Xce^d^_$YMJDq|mqJ^*EjyAN6S|?f%E37hSt1zWjajx%Fs={p`j_xk3P&dF_j(!>og>lmg(mK1Ctn zlwn)Y3vG5Qg*vpeHBawnN#J4M{S^qD)M~~`vXAO3Z6y?(mG1P+)<|t+OJsNGubt9u zL@y_b1mnrzq;R%x%cjkiJVoxm{s`y58aB^vys1sUpVpgN22W7Ej`}`0HA#m%jct*6 zsF&vueOYJz)?5bh`;b`qL9|=4kL^LRZ{A2rEW%}2Fb^^`YEcz^+J)oA;AE@Kv&rv! znKXGv<&eqT%$hPJdP`!s=}CXrib_;ACsYX(!^0cFK;+09+|domvS<6RyM;eyz>uY; zT2^0s=`4--B#-Tr;e~fjrwxi=@uIU2zI1OAVf{ugAq7bbiN77`<~wpkg3VSJ$#?^R zBSh|jbBCX$s0+-x{;HY7R@jqxNKcnR8|B@@~Pn+nw;tdK=YW ztb*)r3ZbhuhTV+aW2^ZuK?dc_Z@ntTdd+&CVcUC1>%V@1aM{l_Ai(h%Av1>jrqku* z=i%O*bRUQK`pw z$az1@-HNeK#BNV6uPw>3sl-{4%k=D5U7%!Em#WP@4?CImM}pWW1%UZ+DI&o;@q;kh zR<-8o^w36BRgG>6c^2TKyai#46rQYEFEUrKFSk5~~ly~u z%ydQ1`p8=EtF>E1bya_?X;grjkUh~bYH@3~Q_7}>5tO8#AL-M3L76z(P2cWmZnZt& zQ?;6<+gY6YlbfLKQO~nS{2v`Cp4)(>n>6Y_P|xAS%9A%LhI7Co9XQe0@FL;oiia5X z-t?Bm9Zf$|t7$9NZ8BOoDs|>e*7&%Fo6(|fr7@BN%^?TZzaDyg20egde|+I}Ue~P@3xqiQ<;i2Dmwyx*ksEtCwl&mHPmH&RZ@aOt^x zochMS-*OaK-BWN0>9m;*M6DxnWw7F99k@kfa9EL89+4uz+V!4VtFVy0j>St6b6DyUr{HibOSfdyHu118eY~p8WMl3jC`231t8vo$nZd`(BbuaZjgYvG3@5%+pgBV#D@5S| zgv0M8-Su%&(rwH%2-+@&#Mod(H^s{|Ncm{DL8m((D zV?G?Uwytl&@6?=gK>VDqL%UqtUsibj*RC$1wX1VuwFZCwri;=AI>EzgS6zJA(L(SH zU@Bf${$aSeEp6Gjo4!jQt{!6aFRNO;?8uiP^ykPaP8BL`s@H zgmt7k-mt;vmL`67L%H~=&^4EjS4a1ESX$yq8zb$hO|4o(R5*`qr`)K@;_-ZTw0me8 zI=3jw$}p#UW%YsmoP0dI#l}Xu`gT>Tb5FOBQUoY{7(Zy3xo7MjCNJ@bn|bnM@i(n< z7Hd~%ZC6ug!&Iy$UBR-o9O)G$Ifh)Yyb1xHq%FlQ%y0R@mh6731$VI)E4(4vJzLROhT003Pk-94`JhPvkd zf#g>6!7)kt?9W3g+4{$oclFl%9B=GaW|~I**gbcDD6~}LblU8qO#jUdxx4@J3W_cH z+MYiBw}-xhW>M!jL1YS+mF*f?Dg4TtI;-{N*}1zHPK~x}C5A6FxoptNb1{BKz_y^pv8`Bl>Y`m>k-WT|#edJY1pLGye_Vy7=I#iuY@ zHnuQV`1A;Nb8&GuHT;-^cM<2>%cDfPVN{T<*PCcfodDiocq`ZV!t~K=0ql@Z^cfK(wz)wDylT8}5f$jT7 zhN-4EMtrD!O>sr$TZ8ZL+_4E+4_d8BrnK9i$MY+fZ`r9pR*ep)xVm|mzHE*hpd$s# zd&T6@Nax%s5A0JjjT&q>W2`1ujj9R1KT4QlN+M{rb0I6JE!>K|fDQsto3V&(n@E9x z>)T02Rz2d6IM6ix@+K0~&Z=8xE;4-5b8K51ot}x1AyKak53eZ6j_sAr`-*sCa-mVx zGP}_gIvBuCK_uRv!f17{7WKa8gdLH)%66w_U5Ssg_rnAn_$|Iw5mS#MXMJC7qoQ*iIZl*Tzasno5 z0DkjR6tLFhLTQE1K3zSK+!4w6_5G{tMgN`KV~5IpcFQ>`ac0qrwmrvH)>q_u&9}t# z-;d`TyU4I-7x3g6GHo?_1Byq?pa;4GAb+V&=_~EPJqm91F<^?D8^Wo7)5*^Z_%jHf zidL2IZso2{J{7!u2&$2npae>W&IV*0WnRIuM6q&nYm;?uk}oDfr*>g|X>lW@qu}i?b7a} zaLD^Gr_OoT&-5*=k3doLbXrEZ7aHyRi9^vZXeoL!cifV&?{rOe# zC_DhTd4^J4TU?a|9St5bE|~}NdF**!`!e$3yGmoeJ6GY1yXS+flIdyCi^lNgC)VF= zdk&li8eO?DR3ACG9);9gz1L0ha#RfC@#~LW#p7@iU_3}e-I=M5EAB{PM4izxJSt*i zhI$AdUPB*vc?wzWF> z%d<>Qvq;6_giCuV7B};iayeIz4BH{IV3t|wY?r%Fl*q6{h-s{bQCVFn?2zm9!>>Vhp)=Jl| z{Vj)}KT6DoSSVlscz;ObX50q;55$BwUHVLaPsS!NXzHx22AV^z=7}h>gO&j27+3JD zM1hL2i2Q^{P;7G7M@JH4jxRIoh)dB_7Np>=p}&Ya|ZCn=ntZF);&r; zn?u<|0r*tuuE6=_)c60>3xMI&9)4n>M^0or^dV7|+{h&C`9*0C9#e&>ac>my z3(&YFY-~mOi@+c`tF0;r!_Bj}bikqC+)!fU`9U@3RIyK>x4^ij5;MKBzS1si7?&9C z1;_%o^jTA@j% zy8|Z}>>5o~e{EtBr9{Hd_9sCrQ3B>4l&Ig~9);ZeYr>*k^_)~#fXDWg5OTsU&-L>% zg;Iz~krJPg*OYbMV|Y3HRi2Xg5v;g+bvz(B0dkGradhf15ArZUyT-jEGB#Z%=OeI= z7TPfJau-{Zu8yy@AVMH8cF9xX?*tdu+6wW$FU+DMY)4wnlDrGuEji`CmdIIf(DLLRcc#niW@s zAXu$Cyiul`L6O5ISLw5gPB(cBzQ%p~^Rl$kyPe*9QqQ57+iAVb{qEh>z3_F(i@0X` z^RntIDqpA|tMN5L&4VcSb8%jK*vaA`Jbp}cyo<=<4x$!U+;fq*P8gf;2U5oO=vup4 zxpp1uZ35`ta6QKHhF*58v%+(RgmhbKM|hM2$*XnX!Y=i`wznIm0jp2>BoH+@YtVe3UTxV<}cA=~||hkRMoi5%6_E3F~o zLMtn4)=>$>NUmJholoQWe1Qzwvdl^}$+scf{URZ$>u`S5-*4_wZbPhCU(pdoWPy7t zy_5_zsgqOWkPh)Ge*YhG@md>o=gs)_5Hm&boR_;y@)7ySbc3L{BYsqEdQWA&>oYYZcS&AmrC>%*FCx#1D z{T0EPa3R8923N}K(|$n2V}anheT}d4h^j(Pz6e-lhPf3{dK7DJFU-N%_M+kT^_?Jy*T_F z(nwCXRsU^qVH)-bPydt-aK_+<489l;K)?uK1iUIu?p9~er{ z*UviKRkxVXvO1p`H!6?HoheMKZPjf;Z206Qry#p8U*CdcD+&@qebM3a9ihEm*IoJw z5>ZWR-HhTbet)anEM`+Szv)(jW#pGOg-6ezO0ztI{Su8{B;H&0`Ko>gzbe;lS;czyrbEv!kmQnQ0ySlY#s1RzOh-Iplz@>p7= z#iO)nTvfo$jl-405*X~|lE^o+#KCOUHWqElb9P^{L zt-fO<5fx%ocO$WGsyc;n?hrO2T*PlDop>M4qlWyQRQQg7jQw~MpE5V|^tbj~g(gH) zQ%jpC4ZVNe{n%v-O8>2?P`t55&F;f+oc)M0pp3AXM#){yzk>0!$j**@lj3eeo2QlE zN=}fdd76ppvf(a%hzQ#;fR%|)<2CpK2Eq_|Wct2am!pMjFp zF{>@Vw4gk^X2}4|DCr61)(h+HFS1NNFT{+yofL{^C*4l#I^_4RpZBhRj7y16)QI-3 zERCL9qK8RzWwaI=u_$#H0OOSk-Yct``!y<}!?mfV0`1Z2q3g0aW8RlbAP}Y&o4tX_ zZ)#wDKbU)M_-QTCXDG$wPZ2s_r(_`N`^q96bJzVmUhMojQYJ3&Cgfy{ocg{=8(77C z40Z)v!(F|_(Yu&ACxD!VS_nyV5!KlF3+Q2nWF747~p+-!(pAJ zV;|?pdCZQXag)r13mOg47e07wL8yDoY)cTSUQIZ$eK$jC!ssE*r*l&K?V3T|QW+z1 ztef*B)WsQN6)5}8f3+Bi2*vl?PnVs}kuMq}$@0c78F>m*^+t!`Ws?-TJ&{{pSJz<9Z?H6p`}(xWOKM;bb*@?j>VnGyh2RmWZB=~fQgX56 zmqtx?(uCO$P(mT(lRN_(*a@~aQewNEaHlr?M{2OE5o%Q7nu*T+Hr%+%+Sb{L(O#qul|VJ9z2ED&{N4c!EXsnNLE9g%%4rk6 zdX5peyoLW$d*5PF;eMuW?a8$xVT+e&VM4=zfm5bY)SER$S{x!#D!1-3D+#1t95ma1 z$Xn<>PL}YEc$=GR5Bpog(R}5g`4EN-fR&+$XfT~?`{c;o3)#TO2e6^w8?rU z{V|O%PA>*7`c6C^Az%ihPuYshhH1MV4P3kqvCxyd_lmAJO)=s?A+f4kmM~AwddyK1o97KL-rZ15 zs0j$ba_XL1mac0f>b90l?OR9q1ZqJF%1vG3Odrbrv9Eo<`e_M%kt1zyNLlc%aKW_? zt`Q>?Y1I28T%{kh3T5uhToat)n$PC?+7^wm%>EwB)IsuMc{_!;z8lbq-ss_vPc_em z^C;UWCawJxHZv-&z1ie#KUgWcJjnV}UhJ4*f4HHP>8&WK~$?{4Sy zQ$9&9B_D_7kk)S#gNC9EwnslqBkoZU6gjkRDNoLtpb{@b-^!DMdV(c9ho;>y(b8bv z#f0YCl~^QJj?DpOLZWvHkEwB4O-XjF3sNFZ)EZs~7W8-hg9cE^%w-ZHj=Zt)d&FsO zw^nvHTHhh?f!szea=w;s8roc3eYGh4^<$CM>uu8fSF$k?S>D#&8I@Ixue1JMHbeUP zZr67Vv7HT-4L3D=UW2;B*LQpyWiaiuLRG(D8cJs!T0h)q$VF6Z!7fbk`YisbNHS6vx zza>#S6@|MdSci=MQ0{BrIRm=^XgjN(kPu#OjAfE~39?W?UpTDK8mmk1U&M}fq^ExEejLri z8_Ujz=)uVm&BD1q{=I@J0M8}I+vX(YSjyG21y1)I3+`H#7jfp|O z1Fb@WQWO7wR*7u`!z_?POB9-bY)PYwBhgQ_DQ4<7m=1^6e&om#oog-CmT!gpD$`xK zsIia*7?>I>5=Q`TSqJ(BDJe9C*d9k(BKZ^uPaYXSn$L?1 zf6t3)R0H!9$XpWkTN)a?9M8*DIVLr)KYK%h%XCqvs`yXbx=L)l9ay zN!Cdd11E+!_K|CWRDgfQe)C^6&C(MblJtwRINwwUF67S~QdtL;Ob`2V?NzPO+21js z6{IulJ@|@;zxKh6C28K+j&Lc_6Pz;&n8)-H8Vd?R-qN2DTkHZQfJQ_JMM7X64B;=w zv@5a;TUkv^%wsJdy)QOQ{DsM)57&?a@Wsj$eexyUl8YS<>3}GE2;P!Z_^vLxH>l+w zGYbvmW^V|EMi+Ej*kfB>1NEXC0)){@K{==`tAYWoPjy z7LEm8BrnDVF8JLpF0{sj-17vtuq33INKn^sA2?mT^$Sum7THJ(qE-Os;tlu~zknSG zEGf&s6<5K|Hz{&W&;adhY3Mg9GcoF4RdiS>%-h8t(G-Y5_psOJ|KNe6a+)P35Msy4 z-ugSPl23Lr7r+>L(R#m?i-1?-QLk2lTuC9aJ%GxiTz#7-JaKi;0y6ZuFiI?k^2;~j z02Ys?qP>iktcr1YzlcY?ONsHuNw0FUWwxyQX7Q3C6Y+iAWGqOAo;};c@AlUOC1J|I zOW5&btNaVcmo7IoYF^a~bor~Eh`=pT?yDCz2h%XuFN8!gr9qHbM+v}$Z+-g-;|^ru zICxEvWs)ur_JmfNx^zvD2QwWZR@)j4WQ3_Kx-Ng)3ER?iA)56oaG7|R*XZG+Y&}6w z^6tj_3U3usMy?COI^5Wn)gv0A-mZw!URPayz)>eyd0>j5l~E<5o+TyXr3>kUWT~ zuu`0SH!>n1cRgsZ|C2Wo&drO7xX5Q0X3Kb|q2k3nIj^5n^a+_BhCLCr$hRY5> zGGmU;zsR-~iR7pih$T61Kf3mbEw0O-d+Pqp%O%i8|7q^aFB2o)R)Gl@HngI4t7AS% zYb(Jz-!U$SmZl4FsyunD#&E6#I=6$6FYz`a$DKtVl{Z#8srKHacD3uO>cDHK4#?2x z$b~IMKBha$Oumnf@JqG?xiX;lm#m!vNAUq7DnKlRcCy|TXL*b8#%Aya`q(rymqdpv z`ch9({bD=E{{=6Os7U0;RlOqhgG1rr0hDS&JVjH3e*;`DVR_+FqFHi6tw#2_L#?~& z3D_M>i#Mxbw|bx^m2(GScc?%g2n@^tCtf1s#FWOEi0Sj!Eh2l=YobgEb}+v79;BPY z*KFuW@jjg`se4mOtCoOuM8jJmCM2X~_a0E;gk`~Iv%hNl7%WN{rT6!QP&VobM^i(I z`|sDF4;}vF?ZX+Op)fKn`3PR+1^_ph zdg|c0QO>&SQGUq6LohR@TjFa$G&x--V7nT7E>d{|O2B!`$_u0L*rPH)A5TX_+mhbF z_-CJA3v2Rg%i4l6$MFPV*|DjbHcv=Y?qWuTM)QR=yMtrw^crp-Mbqf{#m}+giUPDt zPOQrfzo1R7{#-nXxb(S|cVAJAhLT4_hu)ja!vF;S+BQ!O@-uxndyYG#ACGzqc}x9) zWc6#my1$A3G%4pIt$&#+|7^M+sS6wMJf7Jy;FZyYhBZO@WAx4l|*OYH6}zeZvnOflpEUx&4xE_<$i z;Tbq5^30t)u?O&gAn~z4H73zWwg57sYBk@1HhQ$%QMdhLH*Frr^PXqy&vzu2$t3?z zFTh65*|IKVDS?N9hHU_ty&hv4uBIVusR89O#1e-<#-g zkGhrOqE0QVG;U%>Ak0Dfk)N0kA}4otm=v=H$tLRz_~9dHo(s7Io)-H1%qb?&6><0A zo9NY|BIb43BE6G4CjT;Q7qxZ-&Kx3gzqo@SCSe)1;Qt?vl@djDhL5g7K%?^JHqg{^ zy6lE(W-9B(Gm^J8+F7QV>LY{;cnDdMem}szv2wd=pce~+EOUI*W_3WV;Q^1(tX?$d5u+yD40Vo@iK}4}X?T%^b zn_rpaD>IOX4MIaA9q4=TUf4RTekKP>6K(h3kaRGXTeA5g!|aJ{teT=~yLFW-%YJgy z-=kA27r98+#h%U1WLhn3-II|;qBX3X2QLQS{wHP{)*3+L?G6@lMlW`$XW1qz6!Ef9 zxG{q}ll=NsNTKQ0blLV^lBs-(6TgrEyL~}T z=r-cWJ2;sgykN-RUHa7UH=xbc@Bl7;sC#X8xNmh$23b2P4!P1F!GrlwDc<@rjZBC@ zX~3*hP}@m=diS?|!);Rt9@#op%E%Uz%1a&M_QW8hu0!&wAaxO)|Cq1jgDOP0p1J0E zmz!`%O8h$VM^VH3DclU>c;60OP+EsD$_x}8fH%a;W8sDn!)v32#zFtBivMK^0?oeZ zoRfN5-hDU7=7xPs8Ht9nKSc%~3PNnAj{upH7OZ+B1(o3$oK4@jsKIWx1UxK=R zdcs9?IWYzK4A%kuzO;(uBZ)2Cll&(WCFB5Bn^$40Ha|$fsTHYFm%O9`U=(Hxa>ff{ zBfbjll1x4+n({aZ;kidu*8E!E2MLsL?Vk>1ME*X?4~CNSlVhSA5OuJC+U{tKEOF>j z7uqGdl;lyO&bg7ik26m+{EPnrdo3ynAlyX3Zuo{-s!0p%LN0^X5C1s4=**$(qY@LJ zbLmj$+&jN{8V-98yh4sZ$>gjBAxQkv2z4Ey^E?2g;h9P=?kn*}5r@%`4~JKUAi|zp z9$h%t3Q!+R5WXL5Qb)mRtys{(3f&SDLps#4vWMX+hl0N@_#zHzCqFBHE0t^-F-?2G zWFh(gC1>DeNR77zvl|DimE<`f5j7B6Qmg$waa*PZ85HT1gqwbU53#c1L0pkj1w~`w zrIPT5eOF|OjJIye;Skm8`cK83#pr+@`lco_dRbhhHtPh{tOQIe`RdEmY`B-Li;$(C z)Oz%BykJi%bSebVqpp;N;#>E%o}3shLr$lpVDnkyQna~+e!d5^_)c4-7GT@mdqSfg z8(SNd=tV{AwGE5-0auEq3?MpecMcGlwB1K76x6oOc z$Vn5kebd518S0i*kkfJigt$dcW&ndC94jO9c>Q*Ur<4G5O_w<{?q)1@AME!yZ>C#_ zQnGS@^P=U_keLSa=I>ZOlGsfNGH_K4jRrl~YH_2FA~hGCS;ExX!VBPO<_B>BVAm9u z2VW%;o>yq@;wm;TJsL3Dq})39#dX>{?2#<#%J-1RZFKl!OrBH^3@lDmpvI1|W)tcU zD%_(AeTDI{Xrrdsp``zeLzA@Nv!q+>d;u_T8+Sp3-dh6XAA!*$L}Q{DNo4KxJG zl-6wdsE}USZc9WmFCGbGSaLr9R9;#z{*MRcSfHYtdjT$_m5Se?w)y1RmNaSmUHCuBN6OlTLH1 zJ{4#K-nDYOzRvEg30|>^jEoRyuq-b-_zt?qY5MjN?wj7@Y#K7{&EJQ7Z&+OmCDT-W z{lZs94Pk2L%#4h=Eg7w`eJSRLfiMf9IB}%Wl14UoC4XAbMv4$QUx9) zIA~iiVzDVsf3K++7;H+f`RS@jjNUoyhut*Vy}MCVT51%QtUJ;}lRYDd`E%n$;PFc% z-?+q??#!ClB7*YTPt%)b5f;I4-iP5t9^v0i-%6KWpQ`6KV5rj}lbudt^c)o~R=don zQF&Zp_xDSyK=cOgc8A$LI+W}8&Q2&hetaXW>3O@u!BzlC774}v?-Ye^@*F+Bmb>68 z@gXCF>9q$FIxM(VauQP++gc|a$3z(F?9@@+5)K|Au=??Bod_(sG?ip(I>Xo0aTafk zsGAG7_qE+3Zy(rolyzOZH(&3-J!?xB)E}8OKh0f#-VyjlFJG(23s`9W{Vk84O94_rJN$j~A7;>seKnXhJk-jk%k9l{Sqmia|W^MYE9 zTWM#9Ecw8aT66NYWwssYln3%4M~@`l9Ry^peOot+L0 z%D6ANXtUr2R}}NR48EfV!a}%)RQWbNWZG=1M_XKbeCEi)X5NA;UY%i6H+xm3YKnb{ z7o!9JD{kX)jJ9uYXD9x!8}q0jhT%SLf6mF7@|BR#YCEkR*|SJ16Ry#Mnoj?`$AiJf z5$@^<&o0u|R#8rLugk2NJXyJukP7#0^1EI8cu!69t8PRN;`%pPP&>6HD7Uy5qH~C) zuQ+Pp3kOJ{%|U%UN0n6EldLQc*M)Tr|NihYKRW4GOPiu$$?$MbHa9CndA|GFdP>8- z9ARGmbsp`oo5jFyBR)iGr@jGA%3%GJmGx(-=bF@Sj~)NrA+OaWsU4=1GIWd;E&U6t z{UTJ6lQLST+9auN&P4(*RI&D4ievswV*}$GNzKx0TllOj2JX>V=ct4DM`TWX%E(xj z-yUqAMTsuB_U3D_Azhe33DXm&k2=D?XL7(lS83^1o{Fba87f^}Ra|p)cRv=Av+ORs z;k|KFVs1qx;7*8+**eiTtNLN&bIF(cKSn=bb!bvk&1^_DZFbwmpPlnm2tA~1P3D+V z%}rImGzMYsDI=ubBa|=)S)RXUfPMrVc1tQ;J^E=!hSk3!(eWIUJdK*}shZWdc75CE z$dIQ$L7Xvb^VuB}vNVVgaJatcvG+-Q}o ztz3E*WftVN2jaa8^e)b<`}jW0X*a26%EYgnR~bkTuscVYWCQABeb`UoLHBPa81I$4 zI&40X)8v{hf94{5kIv2gMTo8J7eK18=C%t4$!Cd5nA%*p@P!WNz>Yx9)j z2j*&}j5<*!;-H4el^)69r(U3Hega9HWI>r2ed(XEoy-Y-mpOgu;y75UOi zF`V53Zxg!*)ys6qARR8yhIa$seLe|zId{SX`$Tf&GtTpQb&`Jc0kOF~r3k2ARM0kW@LA&Qd9>KcQ#F0{Q0&NzXc-=;_HU-z4r6c4&oN;VaUS0zy3 z-(+-Ostdcxar_>*zur>V!*V}^v|Dmn3Zk3UEg!yo@7^N=PiawY#Ix@^V8_# z8OP`QGstR6Ri{z2N%c#x<1c7L#3yHD*g_NiGaYc&KmO&E=)jhD;4B&Bchm0){X9eS zJJn*Wan0bdQ_=*hl6E1j^xN8!t~<}b@EOyULN zJyPhQA;;7-F+L;k(mK%or~qP`CN9ZwIkF%ttI##axv!8Zw9+|UsS*6LvVsIH+dw3| zi}5&Iv}!7w=fQ%zyE_M(59eDRR2JSN6#wViBE|3}eS9q8eS!@l!3bi<{E4=MUw{tJ zLp&7)E12c_z<+Y`c5lIYanX#Vmu3<4B^N0kgHydz;&~Ll?ZD9PSGjDoa4*YcZT-`; z{e%v8_ZJE(MKKKOB)#5znm$AeEv!wP?sqw$F!gHw_iX5^e~iWNGuNF&71U}j_2y5x z4wQTVJN!8#WBbQZ+{5x<90=L9yQsBu(EYD-;MTHEKBASfD1C${6*%4ku+U7Q>Z_Se z)%x2Xz-Z>3^o)$e6Al3<*ZjEWF{AvQ#ddwO$LERsZg4_H?g|<&n;BLoLy5Up4f_b) z^+Z#wfjVnJ{AU}_fBP6^ve37a18ZaLwpDq6^~y5*mVo)#gA?v{J#l3x(*3W2fzVoY z)(4+oR{|4g%zXsBL|CM^wu~-O4Bv)tGo!g;j;hc0f3`8`LlgjEQ{wa>@S-5G+VcVX zQo-ZvP6{A(-{c34eFP-N&tM)?;k9*p^JCGu{hc>9er-9-tPE#t!)bEQ?;}LWq5~P; zxkJ0ozkIekVH0E<;E;dN{1Mp9^fcLM8mW}MbsU5W9kJs<%2cufR zh2|r%B#oTIFt{cwpmHcoO;cKXSMMEqw`A3uU)P6-=7jBu(?mItmf(-Nl!d>8A2Yd@ z0=rt|wfei-XLC=b@t!TS>sz`xKX@Sp>)~F&j^-^Vm8EotXdGRmMP+k0`)-eUQ z=~`q)xD-F=Ob}|=9auBBMW;TIZ`Y?HfCvcU@Q%x5R$M++r^sIc4K7Rzes427o`t;D z8}6w~>9$nqB1Jq017KYewxtHL4O>#LdD?0`f2r>N*k>DmvwehFP1jSE>9Ucvx%xN8G@&uQW&hE4@Xa21}MM_&n-A1 zg;m{!U5QD|9_)*s*;&*A$#~x#x;lh0ofw7zb}ai)Lq z@q7dra~W=_u7Z-XuW$vM)@6V*WWvW6eM;%JQqk$i?b-~i?C9*wT^+R?fKPO`AEH2! z&FDc>Y#Q&v9kTDajyQrjaQ$$gf-(qYU?=XS8I8pJo;=Gb0C`kZ4;m{RE-Xj$4y^r; zA>d*sJ0YwiHmx=<<}p2w4Id!?nXjn8+?k)}sDyUkiY%OIFZum=>2IV{1$v_c6;4ST zia+>0FHpf;{wRPpfHM|g0XOEshQA3Ys%o)_&|9(@x<6kb!}ypUC5$FJ0MQ`HMY76V zM$GjI3jw__G+b#5OF)}ogHy%#R6+m4!?~5Q5Ji0X*tm!3ytsqgcy!^pH!ZCs+4UR5 z;TtJSlN?h`NIOO?aijeA0dGe>>`CwIT^97NsZ(b^2!L=HPvxF*!O*bdnq5e&Q77(W znb0%x6)B?cc%a5|2I6)%?5;B7NT;8%#N6c)L?xNjKy8GB@dC$GWm+CY0=8XU>y9O^ zYIq-wDLm?s|A5X{e$kB3ugm zjopYlDzJC@=Q;nP4P}j6V>|Ec`dn}S`{#auv1j;sEV#)SGVa@;=kAuGzc=L{oD4`9 z+lf*-sGwLsQfpQhsbgSJhIVE`(_juQfMf=*mg^($JI5x(pxO}TC2 zo8)-d$?pW3lv>Kg?TJS7e2IGX83HWoThk=*r3}-Qu?wmR2kue^-UhAl7g|f6TNuRy z3{gdX0eDgywQ&*L2_P`d?EUXn?R)e2?0sIJ@WaLwed3Mszo5fydHOxB1Rc6#hr^7@ zg(aEmx^bZbvtueFOgA~Hg?>WV;eQ(W9Eudvj{ITHFxhA5?V+j|TIGQECrscBTs$Jf zGbN36tyUCu{K^W}Qs4UVCtDS7axr$jnJVIDV@EB7f|5ZcWf^6rDnlAt;XrHzv^Y+g zT>0k2->-vZh(d+5TAgJ0G&58Ux>Z&4L4m7Q3V$3f12TOHz>LS8hIqyEa45bg=i0!8 zYq}x;L?I2~GboGq@HzA0^&@s)R&RGCr6Ue4BAlmH3gG_I`YNd7vkjy8?@Ex0pU!6u z9j&e{tULGSXS|x{@_Uh$b?xwm1B0rrBC$FBt>k+7%(Y=@)WUUdhDjMrAroWL9GP&4 zHxE#T3_v)gHEGe$YloM&unHHe21F0Bb3Ts%*5H>f5cI~!I%E#}^Emn*Jv*w#aJIt4 z@pi}1V+#`$0rF!n@I~8coL}+=Qb^38aeRnLZGsCN<~AJWjaq2()avi6Lxt+X@1@~t zGWboQSTkC$OQxous@wFWHvSAx{ozZ*LFwC`L7K}UxP>Ax3gF{0G}n#eNx#StssT}5 zbNKhQnLk}{y<;bGYFb)q&G7C}?SNX%ImjHfx^dlFram=eJDa>*SK4%H%C>u`14S@y zTsl7^H<5UU@J`@P3u4$9sCYDR=kk4A9x9Ka26m%}d5jsYrVSOIEVS;uZy^v;TekQY#{of-=GH>(sK?52E0Gy?R~jfTTY}5FO8K{K3 zxChTk%#D)*rqGLK?88EPE}Qwk?-tH=`)D8@TsHIpGv*B{ua!d#gS-6A9g?@`r$gCX z8o~+Em$SoeFep@-`M{jzK<95ns*cqz0fC@^vUBks28hKz)ZswxhXUnZ^jetq9aI z3k2ahs_&DNKi!;Tl_REsnh%v?O-=1JRN3mBRm**67n^>lF(@dS@2zbENg2?Bf+(s@ z>)w28GlZ4l&zT350+gjKq^WlCxMrQI#k*AE zOz5&n55qXz1LTVW3s2sXd>?p|!-*%ExHFa9InunszLii7(B2^<`l+^3MxPS&o^l;B%OSnE(c8Nq=p2Tvfr-4)%!qhY;UkpLs+LmAH zCTRKs3v7Gx@UlSXtx!S8Cv+Pktw^qgZWw@a^J5gWe0YXew)5=z@}_n&$n;b_cvbO; zdXY#P+8LtC39VIti^CE3lSTCm(7EOkbDyXY5``Rimz%c{YW9#Qp<%ytJ05&#w zxxf9VbRmIRx&0!f759T3L{<7oa7z>IzdUdMoibTbkHHep6iC(TaaCe&$7;vvKjn%N zNpiVDkP--Jp+qYqdJ6H{D)c#_19u>bNIYZ}n*9f&bROKMCb+=Ja43pr|6q51q0(v~ zIv@!3HcLp>AOAZI_(1D04P2$c-IS;24bC|JQT{-YhL* z{<a9nrj0aU(OcG*+)rF*Ia;dh%V?&0v#5h+s$m`giBtdof-Ln3(OCjRbyFI%r2=)e z`&mJ*jSAA{=9`}jfA4FGJmj|;G``LjUbgR}}Ccj2a=x#hUZ9N&rTEYdv zK-XU&D>KxR1)l=4RATS*Mb)Ws)^DU12ZV{0NR!!Xkw85%SzS`JqLMQI;W#d1)77zh zy%O zZ`2uMn)kReUh=nw7azSXR-8IBo*;C*rTVLnLKbOL^Tz+N0fFB2%2TriXEQHVy>_=? zx%|(UzuZ^UvCJVHf$1~3aDfID*bQ;hEi!vvwl9UYQ|{jC&1a4QJ9@_Uu$w}8u6?(s z1HOGBwRpDN^pAf)Mh<+QaE)$q=+DxCao?YEl$WyPY)R3GUqYDP|1p;q1F-$9{bfUx ziAyI~LRQQ_$%}~YYvTf6kxyvNe#dn-M>S1)ZGZ;0L12fdK^u>I6*v|3{=inqXB(;_ zSnl~%rmw=&e&%h`j>CdsBBl(%uPEP<&-pwShTl*JO-&AY*&2jM7td}v{Vd7)Uo0U1 zCtF_x#vU4s@U$a$z!)4oPu z_%=%H`najUde>%xu~F_>&my%%4#==>`#I?iq@FK0mTv{?$K`nCB;Su@VLjx6ImpfVoOmBr|%~QQgTbF8W z``_P%|0b|7spTeizcb9%&P_AZYVv7*xKut=lS2JC47Cgy|9i5PUc@MmCxj#K8QTG9 z89D)_nn&s^)WU*b4@EJK_xdY5t>Q^v_#a+zgVimp=H;)yE?oWC z96U%jIok5z?f((G9Jj7i3yOJaTkQnn%@z9tO1osm;~owB&Tu#mhE7Qd#&^-2hHchB z=yGPG6=SJO@=_*O;om32UyaW`vYayODT(hJ&d!>DAZ+>(jZ#s&`hF?hUhS4TrS3k$ zp?by_oOF}JLmveEo?dZ_3-a5x(Ga@j$B&{Ac5JCe8SW$6vcB_^ziZ%X(Qfj5pGEtl zw>{=pXdLDHsZfLgfnO(fNhYiQ0rK;w_ARsz9HPl?NZ><7UQA-?Yqh;@$o}$lsS(ZF zsVi=)D-}m5rg2YIKDPh0(sh=GwyD>KLBaD4Szky0(bwgt*>16dCtqmWeut|Q!<_?% zFlTl0~e6<$7}a7%$f zJ?rejRl_fGnb{BJ$KSM$DQ-*RdQsV}-mECiwpIT(-y^lnoR$rug1%lnl&O%P{?mOZ zwCNSa!a1<%?U)}d{=`EelqicrI449JZfit88kFJ5)h|_N{pZ(%H@lUDXu9xmi3??%Hk+ci1U6T7p+{FB==zs zi&J1mmk(2juC(Gwrmd(z8W1an-;>g(y?iBCC&&1mXdJ&;S`iKvHz?~?=kOegr`V#O zx`pBoLb=smgd1fCLvNvCy@%{L4m!>Cv7*R*FBbm~Q(qnr)%X8@2U)T!OV(&Xh_dfv zYoV-VUz1(P*w>M@QhL8b(O{^FvS!~+l!}b8h3pkGvQ75g?_9mVkKgC~(~Rcc`#P`l zTA#1iIadSRb1k{mVRv+6ujg|+4W71+n}ut^HAE_)Ec7vizAEwHR+GZVX8BuFA4i$L zJy${rjSc>tf;s!2y8+bHGpD8A`hhr4-vXGLbYL;SZgvKxDc^{WDUwe*&_VkfCdV6M z@i_H?coU8!78#%)^8hCc>%5i)OIaO6k=`2h-9ygOxy1C?(B(PIY}Fb_#SS{ZyD*t% zJH{AhO{Zx!#AGS?KXZ}c2yRops0AW*5x4_pCRgmis@jC~_bu+=$O+#R?`x3vlkh3D zbZu?)^~$9k)DddOWZGPk$4Kw#q1wv!4_5XLKd#l~T!OaML2Pw(_CWQm76SF}StZcZ zBmV|W1di2le77Il>P}|A;E(Vb2NMZl*E8{ErRwhM4nD{D6eVA)Ou!)Ux}19dlrZeR zivF6arv+IyYLP>&b?5hb=Mcw&pHcS=qWlMWkG=qtaXc;QO)L3$MFs-9kyM;dgJbpR zS6m?3v;Q{(Ifsh;EY|IOq6m80Pnw^?q`0L*-rCM|2hrnE2E5m?FqW4WY#n|SE$!G8 zZ(T%?H-1sXpB}>-sDcQJ3(i|6Spn4_ON~T+#6DnS*L*#wGhBJL3RuR{i}W_%H?oeo zUf$$@DZVRC|1@K#dTA%^EOpswupl$|6*=w=!xg7$pJmKw9i92qw3Rm#PhCSJc36cx zn=+Bb3ZW*|n9yg~&;%X^@JgLU=@plIJqU1yKd7o+bFer26uziy^whR~fKC?{BLOH}^I)(xLr~Ek$qmv06#3h_FBJitDX~nPX znnkQkKnx2dO;UmOT0bB;f^GGewSbKHp^Zh5lmCGN9AEn+XhEUPUr{U2sWUL!UzdtK zNDZfCnMd_#elp>^|8Ur2#Kn-)#Y}IH??$O>IQ+U`slmbbOMT zQ*UbgM*5EtU}_E^M}-G@N}o}8g#tK3UcFEgea0oNY$mrE4lF0v(x*pugKA;GBwaRHF1`o& z+*TmV=tpdE8jSZqs*V=rqmg1zr?bzL2|!!iIIfo|v~SpD_2giNpBgO={FY2&whcYg zP-@}(*urd`dtYxkgIB55*L=>{l6U-xNko*O0_3TA+DEmv3AO3T34yR?^I)jX6th{rd3a5=V)2}M~zPgEF%tK*l&q2!+ z#v14osFHJta2J97*h%_5yk7E`TEJil6wG>OI8gJoHy8=mqqdI$R5OEAHU4{(nH$%y zo#rJYIexS_1u0HRkhG=B_}C5!o@NsmQu<%kK4s9Q3voIWhk95X79#WGjh0iImr6zD z?4j(>fX=wl;=%1@41}+o-hlv}LX{kw$pMQsnR8(rRKx?$lWDxWcWHDG?Q>`8y0RGH zR)oo7*pf(l@>@lOSMER80o`j=tJ+`=DQ3s@j#{etQS(vS4M7DZ;}X=E3|3QlJo=m{ zBWhq{ujDngjO&(~)L8Jld=otV=r28kxZH{>K&(Rm-$mR=`m~KS|2qQRJqwPa)iU$%)B9IW z|4I5ZKGM?7WR|63)gjP%>fRouDhi4c6RDn)ANvq>1nb;kTx%9%02y(!PwxachS?rc7hRTr0%M3dC$_bAUY8@|lDFHJY{1i)IPTfI3Q4azN{AgtD-O_6* znt1g?qZIWPVI!Kp^!_}G@IqbH6%xeTD>>iJnLxOB_v!p)sj;Uhloi|)WYL}Cv@7n3npYA6u zkJY`7kRw0Cth|e-!!nB8su)jE_4A=!O_mABbR6QrKefw|#gH)FsmE%i!OB$vS8E4c ztz)i+U1ILwj!W19V(}zw`nSbJI@D9tJ2%7*-qjp+teuairoF<@R?-o+nPxb?4 zIMhRWiZi>(;$VyUIo>s4QsbRJ2umDqMAWZrei^niL#wqcQawu_usjO1G8Q={ZCOmJ z)KMRRt(Rg$8-moI&hjm*%RxA^PR==XJU6T@*l5q_4?^jSJ>$PHqU!2FP!!Bryv7*4 zwj#BF<5ij*Y6FTFVXL4!e*ZGvV<#8apFOW-#IW|22breZIc`guf`=d(HYK|WoQ*lZ z_lcv!j_S;Qn(1~iON)z!{?nE2wf*druJSXN)Jjwsdp z;53G1#MhiiKz3G(Xak=wVWNFX9(&fW?&`ZdKz##5LU}(?Zn7ZAnWeC$UERW(n!FvR&=;xgBL;z)-SS#ME(rT9|tcE zf3xCjaPn@3(2h2E7jBKb06N=LWgdS4Dnwbub}Hn6Io!DSqX@=$bSPjl58(-Fgde<9 zOWnW9v;8uSxZQLKf$s7LkFnYyQ=O|&!Y>u?H*$IyUMf0Da3I?~ zD8SmS;3zoa<<#cx9et0>YcYtCdQQ*Z+sB~I^ro{YN=b@U8KFgsazwCD{Qg1tRC`zaA-9&@b?yh1eVv!UI}q<- zP?qlXuQf)(yqqAFpXCHj3WnMJl5VA3+_oJdw1)ZO?BZ?ic=3dw};Sd60%- z1Az{!<0`{{x)t{P+gO-OA}zw2!phD*UH=c{_{ow&r*JqEkwL|8M*VTQMs*V%6{zreYedm4E+#1Sm;EjQXKg7OP_1{oPZdBl1NVYinX@?&McYwULOxiDjq_R^ABH9y*3fk$Hrp9?+Qc#Dw% zQqq%f3y`*%U=u?gp%&oS7sttQ!bfVU>FIiZBd(O+y>tBnVt=OZ(aC^tdGu>t-MYbj zheDQ|y>yhujib=aoADQ_oM44@rUmUzgoI#F+2^?JT!wg%Znin97~W;ZJAQ(+NJbiL zpB~WNu%C8gFN|A>rILIyrw?^m)AIo6vbT@@G=`OS*%~SUhG+9ZodYysvauwPaT8JW z99nJ8d~|vGbok3J+^lzMn+r6e*7sj_wE?;*^nle33Aw%-GROBW;sQ+C2%T&bvlSac z>LFky1dDwvCAz$K-qQQ|mUg&0FtZ~iMDj4w*_M8r^A|~tZ;pcw9f8rnU*r7vmptDt zyPZ&=C0>0dKl+6z%Omr@{C;LX5bn|>sKV%X9S-nU6Ww+g|7a?^WpF~&KQ_+b71KzV zPs*e>9Ef7sLMrm}@rjqaZou};dip)3pm<()hLwk$J>21%Ju|$M(#I>fpAY0G>J(ek zE@{rS^k&>kheV9Sz)#78tsmeAyh$J?s(TYV^VmFKjG7qiQn3WO`Cpx&a{ur_yDdjS zpZtlkHcEQ@zD0#Ja~L|eC6A%d)^ zoq?Vb*| z2GzLEim-T6wi3=!s|}|t(jivubyIhy!p%9FFJ2XAxJ)%^0#J(aoW5&EDRS$e)ga~O zV(1b>Fl-&s-A#JZ9Y(s1;#yYPhkww!pLE|!l<}?@a98N|`B$mq-?Gshapwn_-L-Y+ zzuAjzSU@vH1JLSKy2ps;DF;tYwSsgZ?HO^f89Y3sr}<83a)`RJMFjL2qRpg+dK$c`jIeI$|}ZRvcNl9P) z`WhW1`Qaq569P>bF4&Rmn5=M~GK8;?^OnL@=9q4u5+R&w%?ADvniLkA zv$XdhiWK`hKK3)4yYa53=y=fi2uYgOrH9+MWr0Q#8<4d_ko%CK{%jQ%zQH5|3#aeY zDh)M2Oac4tA13(=xma&_K>=d}Z07JIwZ6!xX$-=@1w3z}*PydhK^PzznNrcdHnMCq z=n;`y>LQQL^nK0rZH|JbR~h;paoohIA8I12Q9w!{V)+~v4H^s2?VN4|#We^m^d{Wr ztCp3T`g_3gsux=1FuS(3*O>TTRA`ed-pRXdI93DM^SZp(>86kq22uFFaLZ*~QyCdc&f^ zDurvh|JMsZ58^bamM;8Gx^v+=Ts3qUsqJB$#GD|%`d%nkyk`JOTA3lp+W$J0VXnZE z0Y>nHB)j({NFp$I7mq(7hS02b^OJz4gS9{>WvcxkVgi!X0>YhzKy!-kO^CX5C!7R< zJOuFu3&ibDBq=>g>iWhA-z%bcePThPvzK3CSp@;oe0^#BQuZAxJAY^UkM~14p~60E zXgdg~o+TO>w!#wlCV=x*Akxb=4U^d9cf>1vb4=!ypTkvrkP zhi;lNT-?#IgfH}a7%AT=|0Z_@+@mJV?FBf%$n=zBT8H=Wa^!9MBSaKQ#}faIig@I7 zo8MLhykKCspuW3;2y1dhfD?duCL*^QXlo!%SBMcA+aXhMu}l@Ws)DPWQHRO`gi`q- z@)NL%#~9&@kw0m2;eQnc-nN^j337)&2Qv1lqi%&U62iaL1y)hYi@=SftK>pA55zb_ zT+TLjf0YPpCx30Wmg(TC_(G@zfEMF%5bZUr?C8W};bQ2{{iLzf-b$8vT1WYkegTM# zz34{{)Wr;XU54p(w;Hy%AVu9^;G24A^eXcgbJ>4XMQD=%hxdKf5zhsvByF>#!vP0# zze8g^paSKs*1LPOZAKPY2Y5UJXMggR?Tm&)SQN}(kn{?==GjAc#B}O_a5GPWwVPB8 z>Zo!SKvx$V!@4 zP94Is3FQT>E%L;r)o@)*`kX!^?0t}JKD4Ee;sB_-8{ z(xFr6sRv2^QF6ua1HtVn#x74*gxK%ipi@0u;X-{!{8e3BdQbs?(C<~tK1j&FvjAIm z1n(4ny_cNiOd7ze>3;ASmdVCO7RF09zC$WRVvd<~p>NnZzlexe5{&^FA8jrVQo}QKmU9fy93aWlQ_#bs4 zf9*6n26!Xhyw!RipCee!^TnJp#&ldK>>%!6WP~)9C%Aiqzhb)ZT6mJ3n(2pganAg}KjA{}Cna^I z_FCP!NQV{Duo8tFC++*2&L%3mHBG};S9$~K0WY#hZ3Bg^*?aDRXFF$t_y6(N z^xOLu9lR4=v6wxDWp>rH04q|XS%&x9YW#0CQu-gM;1J%QzGtii{r2D7ps{d3qDk-TqHTZTL6W)=*iYlSqnBjR& zRnl{w@}tB%(+w)PfZzMD2N&3AWYQ|!;8%YM3l7%BCFK)1Ti~hbIW4lqIIx2dQ9z#J zb?d6ioUt_MmXo~kbD)hj) zM?b}J$2uXR4R<;V~Xk%0?INA`~6MQz)j!~;EI3GrRnbY{2A0#bF?89(QI4$_zX7 zJ;buPNwnfx)Nq<2EFDD{zXcx~33Yj8Qr|bQ%>XhmUf=f(PiJU5z1zr;%Q04rZ?* z+!L-B;GghYUVq+jk?A&^b6KMolal;deJ6lPfr!qyiCr}j7Uom12!Nc$wvTsQ{<%{< z{C|q_Lq?CaHw5~(lz>|NSQuzJc*^_rC=;5pqE&w)NTb+^)LcU+LW;OKxL)G73JS_y zyvg>9cT1}5E7Ge#=i@jCH2w~>Yua^ORbA&MM;L? z{7zyNN7T4#j^~v%yBHF8F?V8O!>Qwu6^%8f`XJ`pOE(Q0^s_2e@nb@*bjLLQC({rX zfPrGT)z&ab?hZ^sklu$N=1T~!d?sq;n-1s!wkQ@EOEUvTh@vQc>Xstz2JLc4HoSir z*{L6b2dC0WktC7eE5x;gd=&0-N<`nd&uT6n!kXF7esN>a4_R1e-IovElbxgT&3IY9 z6x~4c=iIr~j&~DBvNm!P`g78-zrQ_v|6@RAZ+V5JUj2O}c(20w#CCo>wAuxdK={^R zFxdl?hk04yACUhX&&+lFvo3PWZjueZ3@_n%(jWi}D=iY%U@Mw)?ypr1k5acBlDz~j zwoF!e_>n(-U%B=Kfd&%r6E67oOz5(*afZ-ciPMoa`=Ki>($OVWH=3O^MgC7i<-2kQ#dMv9snJ z=|8qJInZZ%biu}bWLvdQ9G1@%j^vPY9ed>ud-={P!Me-!!`Ld^qcm=ik&YS)sl}`e zbUAj~9BfG_w)QSA|~?qYcZKWm$cgc2)X()%sxaQ!G3>~ z=d=D%Bptc-I!I^_N9Qqi;KfM#EO=&KTJ-qCB;h;ggc8-`XqGZHki^q2#h#&sHgl^| zd%?oVlOkT(!k5XFj7o>|vA6ufq}WKq=Hcsb%)NDUE7kCafKalz<@WGGv>7vgypjEJ zv(16;isSb(vvDC8+9!Zl*-2u9UDA@*MQvZFK90}sb6X4E8(vd(()FI8*PVD*I|e{| z9?-{nNFP*26KLi)%u|j79-iq*E=QS~%`~qWH8cJP*qcj1q2J4XrUnUxegB?xB5fYb zWMr6C0v!pnkPC+o$^}}EUY^4?F#pYq2-w_LNfh9&u*rONU9?7Kpgz zVgB1Ji!K7dd)g2z=;tJ zyTwj4*u=A3%;_C&EROGgPZmA<4KJX>Ft9P-(he;S(`_l-DxsdaJzf2PD8T;CF1E(%(elm$39}ML*aDsS zQlFI_rZGly;msJ<-Tdq&yY@TJEN{izZ#c@X8Dr(}eI&}TrlW&G?4J@Lfn*h}}qFb^Q*0-l50EQgaY_a%$ zzyGDo>Nq&Z0;UO!QG;P`F7`8WKjR-DAQ``7j7$bKHxuh28Mi>5wRiQfy$miMh;h#b z;WSu8eZIwKU^Vei`Oi(9oSzM+fygy5v$m8-YG0H~2cLY+Ig+;lbkDM>Z`oYDvOwSR}^4V`~GT=NA6s3tG#_a%xT)ZHvk6(!`J zhfwB;l39anir{yXI~{8)b4g0ZqVPq2E$~Wab^h0 zEpUdgX8V3wy*j_d2t=jmvj7lX;Pr`GakE#v-!V~v`gt&3x5o$41_-I@Pv}!Q_+iWwCc2QC9k#RQG}W<2$-F52!DVX< z)IQQ*E(WWd*IXN2EXy8|`mO5Yhq5{XQvchs-myR;T-6zd7KUTT=8RWKvz>Dx7Scod zJlg?liGLNlvcRWRsiKYxLepNCR4E$O1!dVjU0sjfaf?f6g@N32fKDTZKY^$V^w;XBGK}-RE>9#A_t+qG&=@#auZcb3{PB>gj=C_8dn^yx+0TfD7=)5a zXmW&rZJ}GQ$147=8`E0gYs?Ov*;xb)F@Vx5MyGt@vW1w)&-MdPhJ{8xleheJg9lU% zz$lYsH_2|ne80vBAzy+MiGJVde_ufQMm0Gla!VB#kOyOLG8WQa5`3YOn}%2pfI5)#v$*~=VW2qxX`MfEid1u_ zpRsnr?Pt$NZ#9*-xl1tBU`vUKBC)yp3l%8mnIBy>1tuRl=D5ZbiU^4d)A*UmguU~u z7(|qgF(o=#-#FoF7c_ck$VWV7h7wwtzB$bS3qQF8;yGm!fW%rr1UBGjlfLAVg=6@t zHOA=oHXXHg4p*6l(%^_<8B0ZF&tN*YbiW+kb786Ad#z*R37p+(D}YwBKrQIrOp7?~ zDIzgD+91?%FE6gSyZvRwaERRO%%Q0URcTK4{554@l`8d*9ScZQ_lHtY>893Zs_56V z`81HF$Q)Lzv!oBE$gLq7BJ zZF$e_E@)1Ve`zr&@ZYk#kji~TCmq$?p)5qg7_e=(fS6Qp%fB?i{52Qwr{s6TAptUc zDX#&z3ld?ktzmop*)?yq`sa@>CszFV{Mr|06*Sq?fzC)$+K>cs0u5Ftu}CkDEdWiI zl=L#S_i-j{;#(O451mj;tpglOIBcsZ%nY&9I#ud{O=T?Vy{Lu-Xjd>iXRxO1M`k>Fi^BrIRy{BSf%ofR$!?zeq zL#sCtkmRGk4q@}*q0|!XW!7I{R&K^3$UBQMz)v{H%dAXZayC9bWD6U-%rFvmtv>wLO@oNm*eyijxx;IV+BPlwiN(LfD$g&gkP5>^#S> zunqvE=TGPGUsHQupInU}Nueh#QlK^2ikGe_>%zEIGs0GF{wG#7W5QM?ZSDa8xJ%-< zLGH*>Io%t!(FxS0_&rJVkvhii6~MX^T3{Ih((`8Ou5Y{uqD)Mgg!*`7`!wi+!Ruke z&wits9Py-{&E`P(wcP;NL%NTsh&A62*t?_9c%6Y2C6|3wi|P`T=qRdnVmMbrOqVgVeZYB@lb%n zFJk)pNE6lpKqcnUWjA?S9m>|NRN}u+fhJ37t>{DmkpTnW2GE&fF4UlJvr@s5YWsFK z+YNMs-iKqig@%L{q!@?&jlz#rekvKA%_5QbUy6^7Ks@ zn|+|9t(Z_Bh7Et~mBoYWFrUKNPLqUN#Mu5nbvn2==)rB}- z`JS%4&{QF&1z4X?KyR75%er6saOnLCkE$@+S&6X`xwU6A zxE(QA2$Vvz#rQ1tOfe01$3HR1)@sfNF4~Li2gk0PmoRxnsq&>f23{rXXBGS`2z|C+ z_`;4ksH`oXj-#pfl@?JbK8ELJf0qSH9sJPf8B$RVP~0`cxWm8{pR*nhr^hlk0AvSX zlRO%k>_}2oHP`cR)|=`8_W2iep7hOy(C+K0p(y=Zo*x9aYSGuP3;rTuDw>6{S7E$9 z!@9=@FzE&8Lm7S2LzI2n$)!_J7^HSMOgIU{YNoG=OVtot zwZ){L$f6DrkO6bELe1Ca7)`4xle}l>h>{30p2(9oCJcx$)tL>pF4eny2c$Q^>?Rg- z|JBrl*FHsR8yH$N=ks$aL~iyAZ^GTti?W-?Cp)(chBq2;Tu?-tYCHlWCs&mR=d;8f zUS_ItKgkZHP9Syu?fq=8oojV7{Ggln@xEthVjqL+E;UqUtW5GCgaUg}sIO_2A$z)( zXaC2FU$l^^{>Zc{XsTfFz+)-{LG9z={* zZ0$ZG3`$5F*Zb+hoDGcGi1pj*A^l;nPJ-+xWzA}=^Dw=L00>YqtP@Nhw4513L%W}p zR-EBCcy?``l?rk^;ZMMI@8fI3Hd7CxJgKX-O@lt~=usJhLz*<|^q6abSM5JEFi z=Z8&O&TT)%gt;<=*)7iA+#2sveo;yBMD_;ZoGEQ3Y?5}^e zG@8ZbZ~%D^um!N{!%v2=iQwEeBKx{0qKS?D7$T3TN|(;$ep7e;b?SN}0@{p0fnHnk ztEMESayj_-l;KMZS{CqWh1*5zq26-UNvsT)yrguCt7Ia8`bv^)*Yknd5Acu@bh*AM zEA=obmUhWY?Gl5g;u8I2!dWU}A#0Y>QO6$r=WU&-y-R?$TMB{#j5-dFLG))sccxBe zw#Gkp8pI_2Q-AReb&#)vvY2qMAb<#zw6y}(LTK}rsK|UGm=Lzx z`o+}#SyIFrI=Z=PF6xk9c7(dG*^;DO-x~)e>+^r|^#X$y-hDg}1}zZ5?&8p19`z?k z(t_bfRFnkMnBGsS{<8TRd9@c#NNLPj zbhbzFr-h58i1nlcJFk@m(e)$Xt!L+yNk3D2f#Wgan$qeJJoqUnAgy+_$w$8B#}1H= zh|2~DG6_(GS=Ge`M~1^uk=6q;_5W z&g)bS%Otwtl%z43ppUs*oD%?tpQN)sip&8yIeM{v`wVsPg1Mj?8a>r39V*c62>wBaAu>P8Xsu~+O3e%sR)l^=2n;_wD0g0 z@eCBm+>p}!_9@?7oA@=SqIU`?tAc$y^blTu8e@>+#IO?Oaz^mOiau>AVGMLt-T{c) zVr~Ys3fdggkIk_PC>_U+toy{}=76PZ12XN=)ok8Y#@y%ikPXhJ0Dwdf1TSEy{|BTl z^X>{UT;mf`BKzXWqiEV`%oy0vPEL6icv1oW<&$1}U7ku{pO8Y8L3`WhNfCeI4kEpp zE~v5#4}#D#?VK=K{=J1>O17e*N4$@x<(VtBL zQ4D9ozTTTy%8Dz4)?tE*<~#I#5)iAFXxt;7@%CZPCk1FNl7OuYaSS@1XVOAQ zw_hMv+l>Cbr9M&f`cn=<)qOs@Ju>o&_`>>6V?@0c$imywT$f3p!3GeaC*eev-`#yh zo!I(2jV*xsZz$#XMdSy3TA%_P3I?K~G=ME*+pv*easkJ*su^uDr#8=b}#kclAd* zViWi2Q1u%KV)ARS&AI8oG9kn^Xwn!xHB`%<5Ctr^A)szD^Q#xWfr9c0v)B1^XtL2~ z3Z_Uqo^Xf4trkY6PnpIi9r2GP&jT{#rsYJwNde6R{V~;U(I>&bcE{LV3=hnm8BuB} zEw5r?=*Q`ROf-6>_V3_9N z*zeCCwb%$KvDDA4(Ee$T-}&n)z?raG`m?x?KtJnb#Gr&XRqrq0(V)S~aHOmqJeyeb zGKs8naJWd4ZHBr-2aG_$xv%|}Tu;ZGpR79nV`;uTw%s0t1M0#wd-e*yC5_3d-J>l* zvta_09+`|t7!T9apVgsbIpRJT$g?d30yY(Zn>6qw*gq~5l-Rw(@I}-73_N4#c@A$- zK;A28^=teQ>UyxfApU-sxun;iUm z%K$=Me>-QIGKE{4LackaYryx0D7x~P9vNpSOcd@hB%hgjs0O}u*m zsl_COg9ekK`}BMDhgW>e)KCQ<%*90%ywZ^dT>et_ZtRU-Md{lwfcO^i36n?foD#wG z^|_6=IP6qKEg>j0#kANFJZ(S^;LHC77o{Rhpv;@5g>@SjR%kLr) ztQ&3U=xUXTfc~DJ_jV|lJ!t?aX>>y<;%#UfH2rp7U2y@6G-(40eBHkBxzb)plE>ag zn@+L(CgpEYV&*l=c@1EMkw(KQM*>WBy+>G&#O6;OC0D6@cJDq%ML7;q;v+N=57U1< zsrvan9Jbw^->jFui?nrjW|PWAXZl1A1O#3kutR9WLegxDB~JB@ZX7BsET1s?z1%-1 z(*F*NjOKM(tOact6nR%5VLwbk&<^NTpI&_KV9OvyE&Em>&3fTUaAV-MJL<0Af>wv( zKT@mwCbr@y7y2oV=w_92fAF7psc`mpg&@g=V?pcxV!`w`38TgG=BqBY(b96an_}aN zfF^}1ll-M99r|NmB*BSeRb+?9D1z&0s;^f&5#-{rA=-poHS%Z+uyhKCm|xGfnSr{X^r9325jp@Cn+8Fo-$rAF?YY$twJ*SW~a zQmb7Npm#JnUa9pHz|eFA3+Q=l@B?~C<_e!UP6w1y!HB*%0h}8eci6&W=saNG5g1Ge z{q9(hw|^P9k6Vg+U?-7~_`kOoxGQ(63fqQ}+Fqv+#krhK_wrURcnPf`uf;8t3dXWqV5ZzGr3mMXLJ3i8Wi6=PvX+Xy%)t%t(Q^Nw?3@kjj;iR} z!+;uk_>l({3tCAT(Ch%c=_eE4B4vX*jZdI+dt~5WNG?l<=fG2>VvR>#pkpT@agyYPXs8uMXg9((sHud4HExcL+Ud0v*c{Bng1`m}lYy-f6CoZ-3I{g%D;hLgo znjPzIQihGN+IGefa(lFIUCGl~$waCNKAt1Vqdp8e2c^py!T$)&Pj};&%Tpp*dUJQ# zwvgZq1285_JD#K!vb84kLQ4TnlLy&+*L=q*r4#2{xV9YMZ^nS+*OS9pEue+DOSw0K zhAH{Io^j&NnH^q5U#4gFK~YiN!JI0T^hifg<|8>L|2c^Za9KFK@s8|8NY+pO zQ}AAPB7bhT%YP^=`Hxy0Wk)4?B6~I(?97)j)z9Bz3fJiL8Q5PHZm-QUz3BDGGox-T z-%PU*8go~Hnv|s8;~}ZfTBL)oEub86z^T3IJv~cI1T2pI65v{Jn3O}iX%v*>_)lA8 z*O={O?^p!|_hR(3mCbaA@LK4~0fstDfwVvFHW8vsX6$b_)^YDPPrfUSJH5+ZV4r-q z;+^pIGM3Ws&V?6bWG33aF673$g?ql3&dJ%2s!22e#&J8COM9sLFt($NEnR3gBY|84 zB46>^d$XY)`Kj5xsOId3rr_(;^XF>0fSx~k*;~_qvyN7|(3jL9V2xdpr8AVMWkH*O ze0J&49RwNquo@hXb~XVNO~Alocz$M}IovzUD7`gq(acq}nJoj1xh4iWuV%FSYC21| zPUf>VHJ^lNOKBcNRsKtSH7XY3JsX1{4l(&|RCWh#>{{s0`g~rCaIzfrS|6+?5oR@y zyzXAA3T5d@f4?LPK#3cI=!jHP6|=)9Zk}hA)HH@OU-LJZKA$_|TGF|W%JgdOArqF* zf0%p$s3ZFK^i~L18~Uket71)onYv5P&jmiahb9QN-p?^XFUkp_H8Kc`g6qNOnd@oL z1aI~ECI0KLWp)0BobL9?7ZS_%#=`_0>6%ieW^Ct~@IE4*(TBajol6uq{&Q#$gAzuaLP{0JMiWo4WE$BTX{ z$9%5g(ZxYsTJC}S7Zx*mcoxm{^Eqwl@b2NiRV> zjdOKnE4zbyZ66yhD8~KvZ7b*y9_iv3s0KXRtoBy#IZCbpLx__KQf65f$`ZI#C{ZD-w_nO*Zr`7r~A2=D4c; z{?6=fN(~oUup|5$5BQ^wzv-g04Xb&Sza)6Cdq&;#jslVd%KdC-`-VTZ?Gu{#ZbRg+ z>A5fyKuxA7F3C3J7#^#ZPQ(?~QE;jTmWg%9&K4TQk32c8nJhD{FXB27a<4|iqbp~K zl@aC(1#2SE&6N{+oh798k&?Kqjl%u|-}mIH(jD(`RP>lc%)|aqjK}w551RdJPKB%E%c{nc5MayCZxZT3`e=G&=EKz9hgu{39`7|qW-2197uplL zZhnbUAl7Pj$ZCu)&UUYnsyj`~Wbl=9=G!ec zImIDh^P@TFj84vkRu7aU=mwcEbZ8%MAWN`HBz}O|1i1rK*zY7$K=^4pw3XJ5|0Chm zD0X}gKUo)icapo{KAzR_RC|c!7YZl3<#UcvbM!_36r&i@nbpzk_n%YcBRxtN#F_Ry zO79ru2tr=|KdmMj8bc3WsX2!)uWqbp8A~ze#W(ND=3LenAA`>pMbC^5vld#MIJQ8%pEoGg^sg_^;d1^;%F$3Te}8U!X*qcN;){IT3h6RfFtGOn!lZ^< zHN6z(H}_+fSwhO+16IWAB-Hf$n%e}>`eh-FgQ6ezz(G`o5yOX`4SOau-5L=;CH@bnHMgRG63 z%1MZT-pXwxulsg;c7{I4-?^9H!kx_G`!w-n4i6Ddy4z%Hg4dt96<=uYs&n%&+HqU& znhdO_fkQ)zAtqf z?PAs#u*Mk0+B#-vF5s7e5V#9Co5>=9WqOao{E*peW%GDQ_JQYT#X{qR&b<>IY|d&J zMqR~gi<->?{=&!=Yd#so?$D*?;A5YU_MdJKacp_QUModi?N6!OgU09Sa>;bv+C4Ql zxS)0^T);8y+EC$14~?NOD@vA&A1c_fmmN&?MS;RV=J{%|d!1WrltGe5xOR_I?y(g( zK-fItl7RGbdtSKBlGUC1k;j*BWJiSywCdoI(H>GktL(Dgq+Wt+jhr zpM`i$Tp&MnS++Ymj!om)#^lR9>Q#yM+66)WkV_O+;}<#-t?22!6o-}UE5p%!$=w~l zUN5KidJIo=O)Nx-@Ax_VQz?cmP7qa_IJ34onsTyqQdd7OLh;M1s<7j>JH+T-HC>96xmTHLNrOs?#P zi?IcZ?f3IKjptKe1a@jUoIwk>&?&Bjm&Zw0D(M=2<}juvnV?A;?L3M%j}#=)x+cuL zn-AQ5IUl{b-);RWb#W%v=!lZ!TJiE6H9A;)%1~4s*H-7|A5E{DsF@`OCo7dWjC0H8 z`<2RYUpihlxCaTXc>jVANu#p;t@&KU<8M?%o!F@z2ue-_3+5UH$q#u&aDEj&VhIk4 zkVc`H0&8=wxw$=^ogcZa(g1A6<)^dWW;`m%ty69LkKdQWs|F<-CSMTRSE(v5^H1s4 z?oK&!-sHt@w&WD!R`)#zZTcJoiZ)}MBto9ZM$`DgjoLWA+_%<6o+l}Pa%G`>a4KB0 z$(m#GB%1)lYnJ^bi*O|GV5S+}F3`@iHV48&WLvhn(a3qX;RAYM+i(XW76kXGSn$|J zy@!bYn}m(b_EbvTVnzl-K$sh2h^3E(KA*tz&@+0$Dx-uknWbkeEF z*XgZ9#5Y5%SQ#4SiIlST;dN1>$&sbkq}M&LU4LpbFJ11-g6uQZw?J(iF?w&(~>(L`m=beAYPZlHK=iyg7&JH>jZ#nYOAl}X=R1*N{!E@22Y}{lSdL0 zEGCQYc|q=C z4`#Y?NVeng4&&5l>+72yb%;M+QQuyes)$in$5qRyI5X>PtQ@_8JjGU)M0CPhoI!gJ zo-=;aSuQHNiRpRHsG)vM{3Bbdugif|3OgkCD_&+*nwg~?C%~7T2H(2#YnDrm+a<>T zN7Z`=Qu)7sz&En8D6@*)m?=(rDI|6mPpGMfN?r(U!3^SFf{o)pz$ z|1T5EQs+Ls+^o^8`K9MB)G(`VYJBpcs2A@;5T%mQp|-MSHz>MVA%=yJo&S#O+fwwtwm~fVX&TLFP7j)o zoI98Gq-;x}t=a{8N07~@;sWyEcG(t{b z=AbN0=KBom#w0z04fp3vSt4Co{Yl@PK&0?I)TYWRdYucNgpn5Tvn99b>9q81`*_?M z{)NfmkKZhpFNFr?n-^)q+Zz$Qap2#=FH@BH5U=IVE$_w+tTXj zkHf{YmBv(B<5&_(q){t7u9!^pOp(((B{rd6P2k`qPEy~E9qD4ge7Hz^^`0Be(EQz0 zCD+wkc!>7t&Tht$jIdXSSFUOiqA$#aM`H7$(MJo&ugHA)VY|j*(uL3)FgQ&Tv z>;GnQA*Dy=6345!Jk_REN25@#fd=AEX28(KEx^1Om0hB+P4Re|A5=!-r{JG=$G&U&0M$PmX^GSGS9)N zAE4R$O4Jl{1dH0p*OX^w!4E;z^?rX^zBXz~G*mpd6l=>#yarQ<7i6l*CTc*db1Mg` z!!kKFbISek+XlDrSUxsUlt_dc92(v7mgU^^C7cZX>1@xJI_~G`^$QP)d{_O{5T7oM zIo--44Lz5xr#8v)p&oD7_~;IAnv(UAD7^{G#3{VrWnbfre~=keFwKfsdZv+PzEi|R z&B*ZgXrZ^)w-TlZ=-Kij!>dN$9cu={mtUt8Y}S{BnBva$La<_@_uQmR&6f*OL~qO6 z40=wS8Y-Hzo3TwH&@XU3r@W5uz=ocgs>M@xK@aFp2D`IWK3x@ZHMDHRcVz1Bc|w67 z(-bK-AFOcj)@oT=vk9k8;u08k;R>JGT>86R4`!83et#l+tvlPJrOSceX(_e%0F{5U&jCDhyS{0y_lrJLM^2+m zo7YQ@z-C@pXM7!ZhqW{Gydd`;;Ps2SF(qFk4i^VkNhELRuTvxVU}Q}i1mz5O*iMtF zDD|h|6XJ?t1wyy*X8>4~P}3i%vd9*O%{)f=git?hWZ4vG!eO(-FaP!y_P;fOOD--{ z!XBMrZ!~dfW!ccBF-#D5v%lK(qbYz=X}Fq--Itr%47@_L*4DX$*K5b9ysXY1A~d$8 ze(8C2Q^Uu01V`VTrEv}sA{#>)Z(WpH;N5ALCQ$u2eei2^I?W#B^r-a`FN}CTv5D5+ zXIdEeNzJ}zhA2SJ=N@fIljB^I@fp>^9{`7vL4?3DY;mpIkS36+-X=lw^a$nxFx}3i zR-fua?JH7;NvCI z;bjhpEh5NNAyLAT(BC%P?@u)3P~Pj9oP}Tk$hk^@LsOx!AouFkdYARZbKM2+q}z<` zIo)rhIKEAH3%_hDWr@;`KpssjLVveI<3cr9!52ER@CIJ(E#G15_enbn?kLj&TB^-m zWXM6Y(x@Ef^T&q1WaQYs*{rSEu|Hmt&0JQXAnUh=l-jDKve1Xd0*5i(bGZKZ?@om+ z^Mx(vebZh1{0*Li#c#A+h@gA1AGvy1*e3N}NAyW>N3WvV46Xyq*D`w@VIu#4v5xmVwvf@&UemC7ZtUZi>_IyQdrP1MJTZoVh;q7 zn_f3`FG3kCbM~ghA_n;l|9cb~-o5u+~gD!G+mIt4_ z>3Lh3)_Wq!TNmBi-q>R=1Dhr7FkU0h745cXh}U2+fZVm5a{1}~d!-`C_Nt9|s-%*s zz_2A?2{559pam0EDAGj`tvBO`DsSDj@AL|MoCqL-=viarcTBeX<^2vh@(wdU3q%21 z+QT!IJgtG`t$OcX=Gea#%ZHVW`BK=y$(JoK7=DXC_>B*z;>Jz?!`!CE8VE~HoX%wJ zzVlXIC*+;=BaA^a5C#68vUgoSUyE~EN83)ozet>Id+8^CkIiomcQTo=&lvH=YSI9V zh+pCn>N$S%m;@caX8WxgZQ?)zJSscnJr!D%>)2U91hfa|DN!RRr z%6IIsBE&R4` zqO?Li{##m2l8uHxipWs%8BxGvLvg-zWahT42gxQG_yQkfQVAknW@_P(%(2oCxEq4# z&VSsOjH#vrdrA_!>c$sE>%!lpvE|?FI_EJXd8mVU;q>&=OWpGawc;7G+O3?SPX38`W5#K z_rCG|)+$$!aGWx)-4TpqTw9u%Kr^w(M8El27AokMkkt-9^-k(U)L+?m>Ete7xbtC4 z!dusborlG$6w;Gqh*n8lpENgK_v1fVsy(8`dnlFl-%E7d4P8)ZN7k%5OMdYJDn)z1 zsr5SQvBcOP#s$Y+0rB~sMgl5nsrMc*n?Yc3GutTjz33YFw-@B!1C9_zu7S1CNplsu z=ks~HL`Jk#gG7Wcoo?a{#nPEW_|}!IiSGV}3cV*k4;C#m=7qq=TLf8W5NjKsDjOT|waATYJ3%&tDy#)4ET-}V z=7Sjo8nF2qadS%_k;wYBkb{-NN(D*n^(326MHpMs<|0h%d9X{GHO^m@0o5 zk+R3db!1xe{d<}0xCp<1_NKQYx@|a$(i`D4Z$Cc?={C6&KI`RQg_yZ(%tcCohPzfb)8onehCgmuw*S?MECN|C5pHUX0pw5 z5X|Bu0F-Rx{4<(Qd_=$BmOW?cRKE}BVhUziV^Fk*Aoct$ftrQHC*xb9g0AxUJRxy? zrc-m4D9}QW79gxx5Sa^~B30e_xU4WLGyW6k!q-^l(VM;31T!KGs|Iam{I^8Vyh2_$ zz2`QQbp^UZL)6_S@S4-q0Y}G&j*-d{gI=qbN(e%Nh6CkvCbuUM&x|#DA{4){gQnSq z?={~ihuH51-%vNRA%QMTdO~uF)W8!4*`AqJu)9%IyV3r5>FhW*>!4&Oem8m+!-SLZ zw!!jov$jn^L7Coy%VKT#ao$o#KYk7M8^<8E8GZ$rMUO{Qf4bRWcT-ImeF=8Va+j7Z zg|lBn#3l;V2YLrs@-MAgwmEmmy?C4$PogYxDC7QZ@EmelS7sGKW%L`A*Siv^!aM)B zq(`kgLvw=O5l44g2L?iNgP&xOXp%SEQ!njTpxF^As!|QsRioRZrH=##ttmm_^*qv& zg~tgh3)nP~hU?5e&)Jo9NE7)a(sq-1?_kia;6@568B#J4(| z(=1O}4!YlT)A(U{E7%KwtLRIbR@>kQ=p-yToR3lSG@a$3L{uN?&M+((u}#AY2$#ah z*&b8$K8_xX(VPk+kHqc>$OGBuu{*F`-Ot8b7UE2Y&vs8g#q~8KYp+^w zG1W~LU&v|9|H!g&>^bdsKaLIP4H7+KgIzBB` zZ~r0~JA~yKgtrtkA76 zWI?ZL@X?*vcQi6Ox03+`DaY$*++P>QHwC|fHH_AeGNQarn!zme!B&O~bV5Vphe^w5 z%skMZ1!on~fCayGT%6V#S`OjI04+1yFZ7z|KVhiuYPqkY(acVS(T4RYPhiu3PHOJ8 zWD=fL^_Bk9!ij>Tr96IzuaFy`Xork%Rs!Iao~>JE5TlRf)4+`e&SrZ;wcYA2n$m^8 zPad)30CD^KrFE0 zdI8M30mKfiwUo*huH7XlP$FIU=eS z5@|GV*Q`t$`Svq(LTEm~-2bYptdFmvz6YAsw)AH!;$i)P`_mK;5p8E^A2{jj(upPBt5Yf8J@M0pRG4v%n#Hs0j$J!d$rr zT-r>dqMIa}Mq+9-p9_ zuwcXZ49dxQ#q6~-549yq&0HF&%%*1(CF-?r$QMbik|4_E?UT6o_YwB7NU`61Xpo~- zyE0zay39Y+VUCL7a^?9b)%5Qcm4;8AK<)ByF;Hu+%E5>}^@ zl2ZBk(7S`aL@W}j+XQ0py3u0NMJ{i|NmgaDPgQdw_7A3Sz3^G$IvlEC*oLD=B(a}C zy?@E_?M+~_Nd{4y5T3EtKW-u@TTxIAe}xWPLFY2*uomp9e5dANZtTB=CL5iNg%GqQ z%fMDVb?*ANo}%Gk$M5r-B>z)~=nK&GL;WzE`^yXtWrR?#bC3AaVNsD#L1Dc_QYouw z6)c=&N010zjUU|@t09=~49^oiwcP|%_Y#&tIbN}}sBLITD*H8VddWDY%iR=(ZjDYA z+w8U_Ax@Sl#U`c(J?li45W&h#ge8@8+#{UsK)SNta(_`bWL z*PW<`vp`xhB*of(L1pHFT`;(^!NZ3u9tDxl!UnU=<4L?m*>Zz04KgP+NmBU}wGJvB zWLaxN<0fLVw^49uS^b#RRLs`1_WW2%raH`F zCfX&G9TI}zI$;!p!OHTi3t@H0zJIjpZ`I#fh> zMgi_NI5_p~U*l5!bnxa+msgwooa+(l7Z(WKFo-8_M7nF9Fv;?3WwK2MZ6B(V5@)EdL$VojbFbO+THaB(v*;=2^$vV&J;*5LmSfqOzw+6qRKI;P zrdB?HB8U+owCCB*3D!HWbqNzlsa#@8n+XLmAMZ)dqT!J4i3s{Y$m|L!zC3=?;_LC( zV8PJQ-Egybf$OrL_&X4Eu5}V4fHRVJbtH!093PMxUwJ8;1z*EVvRnqQ5w}FT{o%(m z!Y*A)(e}X6H;h>)YY$TaZI^|}G8Uptxw7Nz#x3;yf+rmQ*a7uK4%M#Ps-%FBN}+p* z+zN!Xo~FP~3V5G!tk0o)tmWV8IEr9UH6fG1<9>ppkO$7w$osLfNq8 zH%&1f`b!XNmf!SZ=WU{(e!2niI9z6*XK-x%SLaYlu17)AI@ko1JzJj@WA6E9>9*

f1H2Q>-D=)~bnE})%%Ol@Mn4<42Z?sK97t;YVpN@ZRHbY_$`Z?`egL~2 zG>M3|d69q&sOVkV3SF!DYrHIcP`eWI!tGG_XVgf;q6ky)XWb(l-&pNx_ht+X5pDY^ z@M`x+5tLcj+_=8@3l^TAm(RdomRdR*TjNt%dRZjv?eKSaR%-@sA+FDibXe>dNXY?* z^?2bl=+?Ble=gCaQ_Fn|R9ZPpA+iQtrB$H_Iv@5$b9))0>kRMN=SrN$B}Ad8Mo=>V zvI=#d@JbrJY%7`Fy4D=n$AzQS3*(i2?<(AMp?msmbPwv%VV0;PDL(R_phm8J!#;4) z1JlY9%|wFz6;TJkx2F~kA|frh5v7D`tq=;t|283OmyvA>Sb~0c!{4C2%QtVtxl|OJ zh#;XWXTf;Gg`xFkQtKi}1-(_YjG?!xkm0viEyP}1OSUJd_t39oxUP%+Xb)~j<$>fi zXN2%#K44(wJ*()g!WMn&tAFhkzi7&^PfIo=m|uqPwA~pyOtJ>YWnYFTLkUlij%e?g zO0{b}N8J~+2~?GrExTZdsxf2S5F$pn^!eJn2P|7yTmI;Ia3hWBDHhg=kgzn1&^vPC zqj;^DT9+pJwY8kZr{=~)vX)Dz^TlV!YF*@CxNaJ%ApGgreb4gHfp5h`qe)f1=a& za-9W1Lk$yIW;18%bbrcbe;*Y@lo_l$H`LgEHXX5ZIvcxMB{*A4h!z5lVs4zViYDg= zD+S|?_QY!}wLtikCyW>W)LiGA&~;;1t$e&c@bv`rlCb$tz_%)zJOVHrAd+SOL~_OQ zU}}Z_@6bY%&KV5}VK5jbfuf^u8h7~rYeV&9^{?~CrBARMvmlrTx1f*W8&0gMeE-0P1FjN!6MHs_rodrNB5K##}Ufe!D#0{((rl#_|W=<=z6c0JM(T$Ch zJ~AbhnL)sw49cQ_sQ^&y0df-TQ6fCe=ghZ21y|Ve`x!Rc7h}L28J*l-2L3L3Qxjge zEyxIOn>W{q@WTr8^QGd^uv9@)wK$?+&cP7gAuqCSE?x5$858u2+c~Z;g;{pdD4%*T zJKnmL_Si9a;tiX$PW{l1lgQqlapBBe)Pf_*?|!oum)Pwf3JjRZSl|t;5fnzt8y|j) zewh12UODC850ruR9zWYx!GvtJpj&F8T5nldCST`}+{7%fR z1`(NAyfSHCBH6!ej6M(G&(QtiB;EP**`C(gu%}-wRO^+EgAHWThkVR?glMSEgF2bsn8e_Xh4 zoOah2vYm*;6~wm%Dwr1U%<1+$UmeSub(4~;1vp3y$r`u}8Dpde=AAUIz+U~vb2cL% z)xB;#^rFLwQilzfZXk^{EP3Gcb|XLB(swbq1VWAB*5FIDXv=rW10$v!4=CrwPTaG! zPlFC+FCwzjK@yNQ+u{4>m_Q0;93RCV2!IL2jLxN`cd$XA|66FC*ed2(l1~tU8Bok# zeu^_4_%5D(iiHlDw)s8b6H^(!3C&R|6z}xId1)Cb;D(qq(BU~WB0da%Q4dr;u#R7h z2V!*Jw2rbv_|;!CGRC1mlHu1J$fM zoBbLDk%BO%B)t8-l71J8zLhUQ-wbzH5!vv^#q~j_vYbz71d7Oy>QWlMag9mnmnXw; z05e?wYe)gSvlV{eK`~<-pHvP^v2I*Mt?KTj*|&dXwX%0WXssOxWEnEhNDq7y3v+v^ zc?dcWkH_OZFp09$VZ8~Qs|$}9@zGwTn$2eA=-Taq>V!Eo`QS#h|1=(7Dv6p z<7bMPEpJoXWUDdjqxVEGdJwROs+&Bz!uyz=3?|H6o<&OT4ywnu*VmQ56`b{;#(0TZ z!U7q-PEgQQbLn3pIG5~*2t7G1kaCRdM=)N$=AQ9kgZq?xq=8ubae!q#B?Ncwj$3d*+l1gJ`(@y*zgB9JtO6qjEGG{1Ii@35mTCU-rE2 zH@vXs;MFh555Ho<@$trlAxPBwAe^MIZ=p_lgLF$NgY}vbnes3pjEkc1vKsWKBQT0D z+tY3i{b_~!=9Zdu$BzA-E4jugQ6}0mEHe?pyc|^P_LOQ9eO7tnxxRqWyirYVU_e z&gas^h;m6M4%1x`Xx_ZbiME$@x5sHM^&eSDJy1Rn3R~t4bDO|&ea>xM?UA7m6i;=5 zoGerV?r9VEq|31@yWBu|%XJX_B3&InHhWw4zkza%`SzumWS{u5*#uzM`9S88KZgw% zkNtQR;*=4n*pI<2(v>-qIf61qZF02pyX2jQ6%n;8qFaMN0V5`R$4$?*OV}ui2{#)Q zDR##IdV7$Ht348)Es7yqN{ACLlG+XID*!^E&_b)hwOzs;rlg^~`HO=8z_9tX$LF0~ zyI`Chud{FFkw<}FmpTm5zGZd$kKPoiIb@YVzX_}rr59^E*>e9)qm9NFkMjtI*DCrw zgTvKS!*3s+5wS~#mEKgM(Q5RV(hXyj)6xQABvO53$fx=krNJc?=5PPJ8!+AqBr z<#meHY4^Lbkrk8~bl-H06b93ftOZ~tm&8DohkiB)NG|}XchaU7p)vP+aL(57z2lv6 zoM(UOSD%%^?-uWF@5`DWb=%j-wP|UXw+uf@SKr1Rv8n`($}Fd@_4ucuLJJ%C)%maV z{H~`1$JiBcwB)Y#mG~8}-JZk6p)~=WEuK0W#7~Iu?d0*GL`Q5?fDixm0_@NKfOyp?*MfMZ;Q*S% zD^?;-psrS3M!q)0X=d*RwQaUK4w@Ge6bR%@3UP`a*;gTbd?=cWsL@4M*bdO7Nz-AHWbLcWamO_tzrn=-#P2kAXs6Z3G!;`~) z|M`uMmWm`gxwHXs!?^pkXP0CmoPM-C%t!GkQpzJFRiK?v;-mab5>8SOj*xPEHqFR! zQ$w1O@h4!;5&)<#?Xbtp&Y#`jh3^MJy7K7HkB{b-GP;}58=zPHL$UeJhZAH5%2SQ3 z@#O{yH0!Y(U)Ug-cc$bE(}|O-E#`+{Lrd>%*;Os8X|k0->l8HGpIV6;Ot7(!k3HjnIQ@non5zPIe#R25F$^uY zNL?EsAvrpEZ6U5OQTzw8d@n=RJ0QX4?k<g(lw1$Tnv|Zq6>XHyo^Q?R#b{kBK3u%JNo_`w=HhZb7iXY7IE~cESurJ4v;E-%f zP{Z7o*=k-?ez-VOhA+EZt(^6-KsDHeuO60Nndej*4!SnXFp{tlDjq5?QyOQzy3l5o z$8~n$C3RC_+T}Muz7}t^613Zpp{EevKMdg|BVgxL7HbZvvP@IK)r0c;LF!N0M>+C5 zCaRa**w3D)RqkPP?J`6@5wlR~ehN`zqAl_P54sCHcY z#zp(}i=9jzSEV_eW6=$!aKTvW9hm8s|K)|e_Yzpws++vlq?`gmeb-6Bx<)W?vrt@C z!Lh_TITEM{Cy0lV#~oCh+e%t3=QYi=j*s;MM80tme-l=?KPlzQOyCn5ciV4vtE@~! zqBgKsi>JJG<+-4G>$Tj-o&5O`7M#ASu&x3EYgOi|gRUtX} z@2a{zX*rYb0mmu4D``ExeTJdTqDdBl2<3})4xZ^Y&1F~SU++7M@O0WBEU}1ZUZ-=J zKWkIe^wo|D0@WEM{{NSq1?mVg;O39D*LC`&Gbvd z=e<^MD@quW@5LgBbNk(__zy*vG!=8t_x8V-U#!@!B@sPOgxAU(wc3&hNx0$nM4jCH z+BJ2`iXi)XinVUk7Q?lDFaMQuQ*O#?d^0RZ*I&7UeAuaaE?n|$K;Q=E_Dd-exhIik zFgg$k{G=u5dz4{^uoF2t?i?DVh?)z{vyacEr0%>?kMgk5;7Un@M2A%7;3Pix@Q|{} zoyCn3VSXd>#_tr$5x(&RE%|im>qXDZo#y->{qM?K`#L-bbolm5b}r_fHJMgmMH)No zeeD=+E`MSPe|%Wkj*%=7`6B8uVR|$`8A;6?#Vnj}5lVB*a!n zkST5ZkUD{l-JX=F`Q%m6+Dm-ztjV|63l!QU84@n&=bhC znU8ldbx>s!WDaH$TrH!S!lkftkRpaRrQN<=oG@%U%W4PO??2%|dNnf;>I=E^0%5_`-xb*KjDe3AtdnjW#w^c#rhc9%uT3#+9%Se`MRGUj7%?SEZ&Ws@fQfkZ| zTUzGC&N}clanlts2OAt!CipSsk{VIwnTO6_%QrGXK2IYr^k9<;mZB3UdQ+JM1f!FLw(^!6zD7}O z)FBGMK*nUD7n)`izFq4aPiReFU%$AKTAp6lcF@IiH56POT=?W=(W+a;x9E}S)|3lY zjC3+Mu~f;LVoRC<*E2lwpDQYztg7sCS~=xhgTuK z!Y_@eVqtW$oahY0-9jupzR3_;?Z7}0F45}95m(Q&PCZhmX%RT3hV>U!9~TcVN`ymlIVSYoBJy}WrCz2<6m^#j&!^eQZ2U z3QZZDHmMjfqHB9c)v(EOu-csLs6EnPdmUrN2@#YUQAH zl5iVU29~xQIC>3PR$8XU`3wfYYunfb-v+AW(`ha_e#KP z7zBU^Df$piaAKC03}w(mqHQOI54F93U<`W4yYtnKpPHNy8P_X4_Va$9!Z7mn`__@< zwHRAM#tUe;yrY3qGE2{p%a>hG_O3&4gWQYz!F_plujd_OVK69WiF&Np zdz!S4f9&`OF65^Q)~Ra4+tU*B>!at)5ag#xf`0acTb+_8yq~J9o|)95N)rJ5R!RAS zn&Wq~)s9bg>u+8UyBD*ybYj+%ouPL9KWl{vC+I5XkNw@^Est&^?2z|B8l`wxeH#fs zj7_m97*3emUwRYbg_T-;x}P_`kt9vE4EXMrX?w?GT)V*{u}erztP?kWcPMqHknntG zj9ssD--Zm%kC7Z|0+HS~X}1J81vbyGs0q<17Us8JkZk?XPtkA+lp|9A4U86iEJeX_f2yX*=~KOiR0W+wAX0kU6;zb@{`n9>o*V z#;vHV%({0EPekkO<29}$<~KlGd*&#FsYO6%>r!666kgmmr0`uuv-NP5^5yb(k(u4= zvI9t)f_UmnK2R&tj;JozPIFbRRUGxJnl`&wA&Vl||6Q2v%8@AF1P^eb?1j05$`X6u z;J3yX>!2A;!uZNY&_J{QYo+@v49t?k)Rxr1k~x@=vDOAzwYn^Q~eg=j6~$GOuyX3|m%z!}wdA*kZojdh3+pS}^ecF6=Yw z+Url!jzNr;5o9Lv(>yfWMe?%W04y0_Hn_E#XuuoW*p(fV_vR=y4eP=@Dd&j1p%N;3 zBVmGmKdFoJ3GKarh8%SY-}3*l!hx2gj1P`NRfS}jIZk)au=f<1muIMeqtd4UYeg|J_ESM?{n$}razvTVIQ7z6ipK|d zX{|{=i{vM3dbZ+79R~4a$rQ$Hzg>w>$TV6#Jk$gYdD80WxH-7kV9xTy&S{pfs}z&< z#7H!BB_x}|0yo(H+J-=x&+vHhURxKAjX@d3)42n zG$l#|)s5C2p4zSI-K%|LNwaKHG&x*sr5S(>!CbJmg|}>N7Uf&u;9DAx@*b)`4n;TzH>&3mA97m8ucM0Hv6^6UU?I38q)?ob zKlD@ZIBr|LaN*(J$h5lBf(oQcrg$zO-$ZXTy0`SJrl+>M zO}Q|G5RKdwXxU5`I5@QWDJygW7D5@?s+`LNnWs4iymkI37mO24=?a5(B#PftmKa%) z={Y#?ltxZlMPK60a+mPPx;5$5TPM1I9hLYZW9sO3`oH;s2!<~z(wgns6Y3y<9eAVr zOOxLOkK1jX;llY#hRDZqcjj(@5gCcKCBdR-{Y`JS*_Mg)grFp{9}W^yc0^twB&vm7 zeC2BKLp7!V_%i-25qJ zwO)e+BIAYo`jUfWbVT?<$DUF;`@SNVTpsAG__shW^R4AjZ143b@|mXF(DeFCCvl;o zb?Z)u6u|rRtqAhWe9`5@?cP@F)1d9LMmFITlUv_wV!wUm++Xs((EE7Q%1F5R;Hi$U zeLd63Rnz8$T6#aiy+=azc^YDaGphy$ zH|NDZFOh5>2Cfj9O?&>j)emw*B65}$1;$a;=phwF!&ax-ulCRWe)n&Qs@}|zL^*e- zF7qK6^Xfo5y&f4HAKil6??-wdeo``;*Ig!Gkol72{bLUDGFDpZIh5H@kuiwM)XvV; znVMOvU}1Wfl@0|fy-Ob*?$eCEvRf5;W7NGhv=|j1z|3HKh-jA-h+=1s4|&g9DUNlmp$4V zS04?U{TwFV^nk_UK|$(y|GeG)PSnf-P-K$;xjBEJq_({0+~uM1Bgigl&WEzVm2iX- zm@ZWc7FEB`GIDUl@UjL&+0=v6&HSeo2Eg7d`JZrFUL^UNWt3>VcO)dxIRb#)+8QD1 z#}-DO?fG=ip9J4qZv`%LZN9EoC&@|tf(%Z)<3Kf(b59EoCMhV_&(&|pEwl4jhVR4) z{d3Rs%lo^m+?1%7<$zdYVg3V&xmV8SO|m1UqgFo2I{eJDQw0HxiZDMG$b3ysIL;Cg z6`p9~Xt8BUIt?ZFd#^h&{@|YWZ!DBcbf(@C6#g~P9!Pm%it~#vkrZU!u6+gzB%;8< ze{k`CW+@!jG8e?&Tx-wc&{Y2$-y3{;OU`BTLOZ>_Vg$*_>n;#g_jka>>a_n<(K34NHGEV~HU6b4Iefbr+-Hof zV2WB8O5cb1S2I5$UR?sB3%})elv+#j9@_h=Al4WJlKbP$o^7#dY#02WCgKpfLnsmm z9jbn%+A!~U2f~D!%pL-0Xso6{2kCKrpN5>o zN?}lJP&ZS@>ZlbTmH>k=N%fU-)QcLyM2IhUb%IxgM4_rGYk_R=GqT)LcZteAAj^6`HwQV)~x<0+QO1P6muzBrv$FRUA-Iwn4TU_h`tV3 zy~@dnA%qqSC$#l@YH#I!{qrnj&4Xfl4_3ORcYl=Bt3N#gj&J}j{&6moFk~h$Apg?G zGIlFVIvKmAYXE0@eft>-YGJJTu{fUb7hV)E9)x=fw|?o^+~$CBmy096zM8#ESjf73jR2#d)u179wJU3CV9pEsT8Ugi*;X6vXWg zJjf{L;KnPjK0`cT=72T~KHwa7-uL#LR*6oJ!~8xnU`AZ4zC2Mh0ZKeJYW03d69p6uIkgqc4i$0(lZ3mYjn17D=<0)kLItM5%H;7<)_G@CN(BX{YOQe3Qo%43li0 zc=r&RF0-YpP;`Y+qg2jy^@V5|F?XG`r+F}03E7gVhZNj+NI+Um{^fp`KR7B22`iAa zbP*kxad}cjlNoyge1Z%3gqqZFi;HhVgemGz2p`x{=4h3R#93}02wnJ-eS@)xkZ41O zjxdmQiAv1qz&yE64?hv)G1`8mi|DJ0Q zSPwKb3cwj3Z;W{bCb-U&dE0Zul2Ykjz{!fax4nDlco*gO2V;`wTB47)34w?$*Sm0P z?9a#0duB+2{cWz%`u;WIH35)=6r$M{>GIaMb=d6#7*jgDyxg$zzCn|3Pm?kn5C_L? zcrqXsQYa<#sdkUbF@iC+zih{T-8|xci~^^C;o(`g5ttD;#7rYZ(he86aWv4oE%Lg5?Z6>ia>?M6ky$ zD%R8f;X` zDvP-Ar;M-+zZ-|Hr6Hd7s=uTI<{o^n3-J=wX`oU?t#pHq2B-R3-_m=NrGt3UjTM=& zG=iPR5@(*JUM!}HCq}0K-WZIPlnD6Ehx51it)OPZ&|vt4;qEV2 zurSW8Y&N;-X)fp;tY=IGRI3g2KyD2jB<++-^;+D!HDlHXeqV(1VW8>MYgM&pF<`9R z>*c8p@4fR;k;#!Gsm&2Yo97_^MT83Gu5aG%^KJk(rk;NsK?pwh;{Yc708>8oCde}w zKb0G&sHZa{j!d_>68GG%{cHY0bmQ>QKev_)tp?lB4}PL3FDk|5S7z1?g3?III|RS+S0nQ}1vLgie0*VN7wglrHh2^5dV6XK|K7N%(XcyZvH$DuX2ah2-jbr%!N!MOP(xa20?$!@Z+E%*HB@Nm z8E_L@!iKHtdfN{@tO{p!DP${7;{4ee=oHdT!i}Me>%L8-9;Hf;%sbhSU{|DI<}@lF zNpQF|9Ee)mjr?Y01jo~AOr!Dvgu&T}@}X0O0p8`}A}&P%YBu&4r9<&WdpEXo6Z2Q5 zHtlN1qFRIx?bUU%BsSuRHse8BAlX#sD=%A}w>$Ro+^endoWD&pkR(Kmr@ z2liKeh?YNm(=-*Xo;TMmbqP9R^&8S-=(SdO$j@sj!ji%O8jzNK%~pFK>bxZ*k=wso zEwpfcsT)h|QoSM_qE>gEZ;yq6vt#Og-(e&=$)ZMtD}i6 z+C0+F6jkE^p#`%uMc(ox&NFg)$0NtrobsZonX0UYjq(Y4z zAS)!p-S(LHv7R>gXwJp6UW?=o$wPt3-)=Ki-|qpvutUlJdI1grldl;LzsnHWw@Z)| zBn}@^Sl!IP)(a?DDyd!kb^H}#!Hcl7o`ZOhW=3@`-4~#ft)@Y>CVhlTP|WUB4nF8GQkT4`g)6WFdc zKRmtvkDABvPoO6bwPB7@(uV0ogwNiI`a4>r8LI}ydw1}#Pv&Jtby0wXp$2u35wk|0 zVjTCh(d6d>4ASPtW0``^Om}dtbwrLBK*rB7aly_v-h*iceV2jG_xfbdJ?_y047I#& zrrduc)N9ASn9}0zsQq2}S`y^t%gD2>#~!2Y)kbha#BbaY}Ue0=3TYW&3RmHX1^`OASK40tQnS)X|_3GEO89-~V?_yX>f z>in-ExTBl5MuTu$lNtWIvgrfT#}m|6b;xpg*5G?eIvkCeZ^k9r7j}r|2Wy(rSC7qn z@v}3ZBCD6k(1Zsc3b)h#qTa8vrt}>3qCUih$k5Qw_jic!yG6yh6@$mm`zk`0lpxPM zU-mh_P12=;8Jyq5%8pwu6ItrD+VUMBLe%(q+AGKJuP;$SO?FAZn9kY6=k%I0 zv(!9*i?P&HqC7}1+K;1224xQZl4dkQwglV>7slay)f4KA8C;06(v88VlGU^pF|uXC z4XxPNl+`YZ?lslx?QT2w;e4-)AN4L$1OjE3x~H)3*a%&qjz<`5)~6p_zh#*g1^g;3 z8N|$XK+g8E#JE5OGedV35fXjvKF!ADhk%*U9mh?b?yV0qIF+%tz%OB*J6*bnBR9f+ z`DC>bKeP7#sbb@Tj6@6lZ$N3#t-dTf!M&kibDM@tsW`)F`h7uQ<^W*p3Uv~Rx=tfF z!kXdkQ3lo=01_%2fL z#WcHvuMbHTzBbkevz~#v>yD)04Dt=SDtXx<<2Cf=+3kh~c&7 zA5mVrM?YZ##EwzYWhEy7fVq%=~S?K&=eZDsIWh=1;H*)Ufx*L$vW) zJ=lLgY`2~6RgTk#<;(Q0!km)WMm8NZ##=yeYv1ALjut1@i6Fw!^79!(t5kv*alin8OfR!<3JH(o=epkB)sqC|D0_7^+kq7l0(_B^eE|a4@-rJ$`TxJ@(agLI?RD{7}owF{ti;~7 z`@y~+%Z~lf<9QOt3_P$6n?n6p(blXl^?psk{uEc=nI6vB`_2FduH^!XKw|FldZ6h{ z!`OB9j*%kHZkMhxGr`U%aK2cv&I6LaC$u4ou#*$J&5*FXqsxk=tP`2De~g#*o}-89P+1%IZU z`J?tJI|hoQP(k=!vlG}NJsr$FiuqW(ukmxR_HhOI-NOstHwEGJmiE|{L^?!?x8BV_ z)yU1SV51~7F7Jw*XW&d#Jgbe~!_z7NX7+kWjG*-kc~@#sk2Mw~HUbhet3neS=*Z>6 zj4ezesS2u-Ckn@h8APJM0*cjHkDrUb! zk7O8?)rkCxBXL(QXMKm>q@noYQ?fh9N(V_sVM)jD{I@&f@tu;v1PtnB%xUx$lpa0O z@w=frhk4HZ-$Wi^3XMl%NlF` zAfs28w%Uy9++zY-Wi4+yq9hLJRSGSrc+dzs=IbVrk;92DQ_8(PM%R5E~ww& zl`^u|Un?=2dwO5@zFayamlx}N?Yv{flTAZb;nXMfWQLSyJL{(DTRKThd5nxbw7k#r z04Nhv&f2FhWB;Bb!TsKYYu>{Yi$Of)1BEji2E+BnV%!wMm#A}@Exv|Qj<>v?nf;vV z7kZp0Evo-*)K;tHVxbI?6~Rr*NP30^LeUYJP2o|2>{5@9xtGW&6C*bU->He322sDL zg|x|iHj9K{J|CeL(kO9&D@J8D6`RXz3f1FMVxa9a+fnNfV%{aIkvN5*WP~$Ku<>!4-Vw zU*1nxE%&-ANkRA+V4k}s?*Njzo{R~1TdfG81B_Eg!^Ma7CS-(s0#-CR);?sxycqfp z5{e!-C^1a}B9BOk3TSwCdmJadI>(4w)lyv_E>Xa}_qEcBD4Z$IkzE7*8*8Lw^a^ z$D=CzKF`49G&!YgAGAZ8iT%Ma*Grp9%JedP`4ug`z%TeyK|oaS=TlyAM%1%dURY9> zCIg5GiS*VAZNHqKcHbZ`8Tk`-@kRyy*rA&~PhV;9A5j5wHoyto_&`Dbi6aZ zh=>F>j(QM@!ATMIumdXNEur{MH`A{;1z}N`SMH_0_3N8Tc9AZ* z@Kc8EVq^W^guPkJEk7ye^de~YVtOR0aNz?iT;b{NQEAHh^DIuaOAKpcQC_Z!yU){C zLLrX1n}9u8(lj+#0k*e(AQM$npbiv=26t1J+|&nr3;U*_l!rVq;}p3N ziA^H^E){wtiFJg~60UK2&rY`v+|yOfl@f}vU1VM?Ttfu-DW}(~zTYywnM-%{J)^-! z=hzm|y&%FFyYR_>SK3KTfib_xcwuffy>ZHdD>t}RiG zLGkTgewSu#(2r2HGiym44a$$})31hY{jh>EQg6D4lUh#p^ZfKJN0ndydffwP^(Otp6!Wk9~TOftL$X zRr>9p8IDVaNP_i)k$=&N-mmQ1HI%gVthQ7EHsg&f54wMHvn4c@TJjsBeyB`dz&#Rf zs{BvsLU}0Ya>?x1$~0cW4HQfW6JDbYhqR6)lsTccG`Y~x7Dly`1*%fQ;S?Is@CZCVWazGwbMVp zjEA3ApixxML-vA;tVg(8A5Ir>E<`}hM=gKQS*0m*0a5M|2v&hOrE*VoPna1>T^vML3V7S0nQ|`Yt{EL4CWxR$YxHRc zG}i2Cv2h#^-@1q-xurTD6c|m_JCnKq^#koEB{_-48kbh2Z)682L4!tyHF_UO&Z_uF zPW!O_Pt0$_inK*nq}gv+k?!b<#Q(b@dEKX}QMxYZgg&Ql@{4ygsaisXtV7)H%>;fK zi4#+I%I4?LD#`JQ)>+%QJnYCGwF@DxG?gifi617R&JNDRt@d$)bg|0I)bq{NXdIR_ zsc660Z-U5h>z(iC@^?s9+>2O)qpPM`vc00MF)k~Cqq|;1iYT*nFrxvsv%+sfA4zF+ zZ3+Gq2BUO7``8uWP7EFa!4~{au(Xa-r*{Gv9DA$?uPu;v299?RGLi2ZJ4!AheYuHN z4{@WLZ7YTe@zK@nSm$1EWk_kh_vxQ+ru&FF`xwPg77huxmti(X;zz>&(MgVwwyLm( z-)7t^*j~_LJ!05qvr>r)mlO6equ2G36JZLHv& zXuQ0l8T!SomLLXf&$*0vhq=!Y7JJlGjW`(SpgL3*OP<9%8sY)o%7(2oj)=f)wl%uw zfv69tesZX-(1`xaCT);T2vt+uDt{meQ0_kq?rNXp1nSq@0z8s)eBR3!@AdcZT^ytq zjB*j3UD^J=x!ic>Z(#TqUwnoY^FH zEkontsMNeFoVwurhd-(^KI7Ok7tx?X1xZte!R7jcVE}3D2q7D;a9l4W7NX*AA*sI( z?4YE^!CRb4(4_nOGymhu&;A7)B~LCVmU`Unxy3w0ra(Zip<2GGIkpN!Pq>&$S^24| z;KTu`KVIe^OSi)tc!p)mUH!F+hTK=(s?CzR8|+0<2=mFD+MchWODNAZBGeJjvS2 zaK2&gHdTv(UWUrP!V48%yQmd#rZHzX`H%psw~U8Yc(7;b9%7IiBk#*hf;8t&py_^n zL)9rYRj=&1I~w=2X*mNaGhLtp-q^c{xlIU`6~rI9o(B{urqkvA zQzV?aY(a$8Y_ma&bSB`7$FfOg*B#J`1&F@-GE+vJqwKYFYQIXEREo^ucOn4Hr$L13 zvcK)0cJZyEHBJNv66hQ_9+2vDPUZz|AM<9VF)5F&f#Uh|hiKbuVs=G<%Gv*% zj7(nUqrE}4pxj?SsqJa3oeK{oPd?i{T66#_J3)WOZLCxMGcuFJ(&^d36+jZaZTcI! zS8Ms?%o~04Qc7g@K)19*2J@dPhuK`FSu4B7L=bQBL#|QiN4CiH!9;;hP=X}Ga$lB< z2-4cpwX*U*;JKy8D5q$xoXHeA=Q4KDvO`+GfORs+Z}vx|w|qEWdz!0)?4xY<67~gS zS9$UKv+lF>CZB`4Tm{g>?Jx$1R>MOqT)yr{2E)6N$3hcdcq^J3wI44H1~*GYsNAHk z-*)WYX!|{Q%FMYeHR`O}F?CfQvNEsDZp0~|yUb^n+RNJNI15~9<09`~9&EJzv#LW| zVc*vq%L`rLw?#TWd!%GeLXI&cjXZD(%qLN!O=5Gp?0dJim#)gq%fbP><@g^)Kp}uDV9C-x>1Fpes}(8 zX++;3|rur_~wiL+h8!~|Ta$?nMT87La4rVL#a=ytbQpN9igw?Y3 zQ7nXWFmRzq_6Mc@P=a=l?A67MD#+SIy1YjLtdRF~zRwA6gWqjwF|P)bSmvxXtLPqM zkQ0|w<6h~ww?`hB+v0=^tcWd^5!d3)xMyZ74%}H*kRVM9fO3u~P)e$ce^t-9o9=B> z+iJ^r^VmarB%A*O1-eNQhq7sv3@$un_DXZFx zyp#rR^RdMfZGw^y)&Vfqe>IG~ai7Wd?+!;Qn;`X$leG!WO-$$ZSWwO*Yj7si$c5iv zUa5b+e2=)dJ#9eGtp(Obk$EWeO-*LGDxAub&MPw4Ulst<9(*FTQpK{FldO>=!yrV%bS#{*M>yMrcR5-&hRQ2?O4R-{nz%thjgo z^f?F*Ydu8CX$w0beccnTlH^~fS2Zb;YE+cGZP>^D(CyDP9OOxf@jULgL`mUHT?Ta( zN)`S`F^K8AL#ZbReRF8z_=|A!Ymtj@AI4SP+YG0F&`j=s(&$>n;CtPY@1>6aDZ?N~ zPXQ+0SF-`_`}O}BUcxEft*a!WbB0u|IE#Dk6$;n_Co6T0FFe6Xnf(T~AA@_k@%YWg zg=O{?MS@pX&0>H0Zh&VuCzMsob5s!K=k&;mmD@_poQrN_nF?HI>;`vl*+6rY!jcb> zOSZ5CepS@!&Fsxg?>a-Mu5Z+qe3sF=^+RRW12OX}m_6a)uz^|bR&?B2=RGv)n;OE& zKW<9ve^>(aRawQd*FR>ywUJ_hW7yw?iw5!S9;Y7DdH_PMAA^d%%THz zFVFd(6W)mJU&IFPTFgGJRH^*3w>`AcwbEgcCP^Z~?~odie0u(bfJu~2f(VO~whild z2XqD9;qt>Em3y(W{XMz2`!_*TDTNB$u8J4F&-RqhcfT)O&PgaV{#8aoaxo84W-u|% zeND?Tqp~%#;wrU1Bs6AABzp$leb!(lCxqG|W3gyf{nNEcFAwIQ zK3gJ8eT03*(5{U5v7NifgF0uzxz05EZ^$V3#u%!jH6PfA3~DXCd?H;u%ZFpr?eXC( zmQ7qIp0yZt=00tfB<++~XcOruJaSk-YeJD0V2KUWmDQ4D>CD%M;0F$ z6Yju?E5u1Qt15}?B`!p*PrZ@SC>Mc`m=5!IwDE^5G;Y?14@+vNA0w`_`Dd#iD9&rl zteCONivDutuK%xD@dYXVq0EQLK4&r8g8}>L_0v$1FYP&Z7o>f+=gVJp4!5+OY8gFE zf9P24@4{s&Co67y+$mxa&r2PJbf!7P27jLS&Ags!TpMDCKw4{#88FawC%Fgj!_A+NyP#|23JhZ*X&Hx6HI$2s29?DvV02&C7I_1(5(u9SVuiY&Yd(R!)V zzboc8qR>{YDTThP4s})FnmDVr&HD$u)G=u{J2?B#*z1xP@V`7-ZMVPMV!XpHk~UMl z4?}A3hRxs*j2~j#W5$iq|Mpzs|l1d2Bme!o{Cr+Au+;AO1g z)iWNB(g4P^n@W1VU2)OHrDl*i2?pyu0TQ-B(snO(5(*4>U*&QUB@Czw9< zA1Z`I{ZR(Ub>^;V$?G7sT+F>g1qlydhuR`@#ssf%7b^YaKCUl-kRbY2Sm)y#ae=>Q zP}fuKuzQTg>=%Bim5mzeiOCUIqBlKBB50t*VjMjgJu zd^8*Ld5Nc3o8YCR(IuQ(u4g@(-`?!B+12dUE@2X}Ke8W;;LPXI&+V%W4s9%80s}9> z6=8_`CQe}y;&j_k^mNjh-p`cA{SQvdRj{O+oeJ?mw$quXz29pPcken+k9279>gvoo z+RT3Jvry{ira{r=+O~e7=?kB(G0G}Bno|OzbWA`dFLn5O6jsz{ACgg-nyA_pxs=QQ zcw0A?pD;j*pXnV!IzU-(uIX#6N=rnvb9x>ts5>gL++F_EB(cOGyly&-Y>IVe!;Z>- zX9jiP$5l)HPblyVVBppd{&_7S!ak`QZW*MBJGn4;iOS%^7N9jQ|v z>wPpcp0(%NjSe)1=(jU5ijUUmXDzQO%Nti+K_M3yOtI9!Z7V9u(QO#sC)Kb?g~ZmJrQ=jdbMRMaKaw(1s;XrUbswW}pqXvA#QW=@My zDn~K&WN-OaTmf#r0socz5XBMd+Fcf=KRM=GTS%-iVN`{`EzUC`jaSPxg+QOV^-~=h5@nI)ccALkvHu!|{}D5W=w>O9Dj(sO z3&=v?3V~a71N%EY>i#VauiKFv>EsZ!u}@|ySFxX+?QE0#`f7J!9zpzAV$a4!xVnR| zz>=(gRkFv6@9&2;vA>gB`C0|#Vd2}3T&SpW`NGQ)(?P22Yre}{u*X*ah9CR-$1T-Q zJ!|2oCA4q8bc3^2#d@*qdX&8JH!OVJyiTbeR;}Mmj3<$fzcwUSv{G`8e$?&VeRvP| z)2}oRWzl_5&Ung5@)o1=SUM+AIEmYrO6{-8AS13+KtCRT&=oOD?^YVSPHoR|uC@&3 z-qAWkq?BOfg7;Ys!;EJ#$^EUQ2M_hBC22NphH{r2!1}E1f*qW*^oY+Q&EN$w2`m$+ zcgmcQ`;#N@tF*NRe!f}si7ysCZMl7C8AlJwSRmp*Wqb#SlNNiwFn~H%G!;>QbU)()R{@DOp^xt3LCoMV;WgE^<#&$3i4sm=9d_J1q~m*? zlpQbvafXla)8Q(y(2$vBLOKby-mkw~&v!z8syPw)*=SMRE{bM6ZIiB3#keO4lm42@6_+=T_Gvgsq#bllu{!Iv+cOoEx6@;232 z6_$v&H6TMwx3Fx=hq>iUA6_0yiuwa7Ho>2rJhUNj6m{;PlvsANy3@Ww*;uV5h+oia zBRID~BF*LDJ#g4h@d+LdniC@TL}9I|WfhlA=tu9ru_GD-`Csj45^XYn5yc$3(Pspm z7x<@A(N9mqE3+o-YY&8Q=IE6&38b2A`0tFUfX%?T?%PyZP9m3?@aeo!U*DkjofC_scUw*Oi86(QwmQTck<|TPC`aU2 z=te-RXIy1H?wAgkre6N{bCVTF;{`!RC#&-X=przIxm$Hk`S zS!Y^N4T@j!d3Ao;>xO*@oDP~Z0|`zfeuzc+fC2IsH4VZ3$L-6trfn>;5{eG6B6^Ri?vCojpV zeKJt}s%J3*kvpqBQ>Z$x6Pkp8*E&>O2q#$v*s&2ZeCMlgd-J)At1Uh8;+(-Wrn0Qp zH+F9<-J%V|rZ<&23jCB4aJSZdw8+*a z)MX=pw7y!+Zo+IpT2)q~B~RfQUti9_jPc7~{`kdPfxm6i3d(WpX&an>ODKsa)Y)(C zQd*v%m~R@{DUVexo3QdV>I#oo2#@4QT{l-B_qqtX^yWD}<@UNQ+DVBm8_(W8{44*onefN`Kp^y#2l317(pbkPqD?zsV_Cz%S;05iUe+3A(IdbRKm3-ri!pX(aZxtp0E$kUsr(C5s2G9WSE>ws?yZ3dpt;sCV&S?b@(T1O*-NnsT1sUIvrd6Z78Pg~tjRq8 z3ew2A8h6g=Pt1v+kBtuTbJ!2y+uK>at~I>CEvcgaIvNtX_iiz)CG2rE3zhQt8!lzWu}1?@qKk2A_JvWlr`{IwleMbK|bj zXBRF}A-zwyo_TX(g$mSF9${XINRT94a%hudH$SWNoJ=jZnLiM2tDVMchJ`o$5`e%` z(bRIj6_%;yw0+e724=8mlD~_8DyOlI(6{W!i{|$MSFwwXqdIY&Q7^-4TJhKk@$-`V z7mXP&yG76-f_t$(_AOuF<>Y`im9n#~RrvAl4)@=gdVOzxgFbescVSw5CPGcs*QO%ck{fg48sqt##4N?92g@QCujed+Z%Lk(sj`nCgo2#f zzqH)Q`VC10bm&rypt?il_*H|~k;uaDywJv!5Zpa++E*Izw&_@IMP{!bMmUo2o~{ye zasp_`#oXOlV0E>hJfvQaUVs>rK3mr42f&v86_VduG!&P^H*T~!Cyo!}J2}yCucJ$C zvSMRzi2qMMCNr*%v5#yi5A)W4!CXHzXHzjie!Wn=euLW0V61Rbz@a3*bA4OXd9 ziYSpde9@fhMCd_Rx}-{NfAZ*wb49gTFuxEprlxi-BC1@`flEG@zprup%Fq`v39+7B z?cxj}``1|FkAg4t12@<>K5)JFHc-*C7eIP*WTxIfj3-y?SxHWT1Rus%*#$}P_0U0h zB{6RVumQ|#UnpVqn@f(v8o1NqQ0j7zNqt`&=)M%Y8JLPwwGYtrv zhakhIX=)VvsKy5>x8|Byd~r6}gPW)bM>jKj!IZcOk1P^BZo`J2+SzF6Jz%W&>EXPf z1qlDr3$Kz8WcjGXWJ|8;nHyAsh1XQ{dgh+mbMHdt%dq$T+a5HN9BJaWyK0&z2Q!3f zn$1P)>QN(3j9HPFh=<}kh<@+2w~`x~C}z z-k+0g?Nzo>i(GoAoYV8=Q6KeY~%5; zQ1Cl&f9oS{{OoR-&{VI1gevq^nN3Y}=?*lYl1)z-884n)$P7^dndI(%%d6KXdjtyd zcOFaSiPG7^;~P4x1Wp_%!+0)*{$e5HCNuCaLO?RIhsB3y6!aVff!w}!|yq9y?`?^96MdTEUER~9%0C75= zKz>ix8{JrLN8P4H*Dr|7X-`xv!J&a1($AShgd;=B=r)DEVOv*hy(wV%qNg-d{fBAv znr6(tTg~hD;L}Vtx)nHq)S3bXnMN zKHE+zzIUUF-{zS2dP(_j(j?b8`I?U(`oq=5d#5^;4gPyXGbkh@A%+>7q?ET-??2&C z^>8nBZ>)^!yZ$mbW@7I3?gtUyt~S?zPp_5gRlzWk%J8`-m9+c8G2um#2Q%iWqIuNc zJaJHfufQak&(AfSs{US`Us?fW%Nn(?gNpX~Or=1KH?=ann?^W`Fj~(NmjrJ695zVI zj0XA>N${uJChz{>ewYTUnN)n6n7{ii>pxnI=K?mAwpU}fY71CUJm z*jLkBwZXI+GjDjsy;3WVNZK~Pg7^e!^j ziwkZnFW#KJU?v(Nc-xSA*WA?!;>21>YqoLOZq~3)aMayYr?|g>W4gP1NeeYDILWI6 zF1ylE<(QCA8?dNZv#qx+DmR(S#*KA;O?ZpqzsR;-Mw7bqBmA22(4&K{xYYtuOH8Vl zQQ}j>J*5VGa)*`2;@1-U8FQHcsQwNhU!tbaTL|K}MppCsPVh6yZEX0s_b^UC6SFE< zZ6kIv(g~VEvb9E{lXvHTzTe{YmY-An0WKWqubqd2f8?OdTjOQ%0WO=T$7WH}>7t3@ zyHVj_VJRQFeSReHdC#%ba$6NCcyGzRGrnd!1q~*Lf|iZo%MJUmlCnRY-yZQ3xk10B zO|t4?r3W>SIOHr@DTt*Sk@=eNow~BP&v!@f#g6=TX@JwCZ{(6<$QxFWaLry;M%LnP zjjayBL^BQzojl>28Awt5Q{1<(eJWNCZaY2@RghZi~V8L=Cbg}Tr-0?B5GIAsz1aR7e1wguWKn2g&FJIZA39%kxo7dbwbr`NL8=CCO?fAuoL01$E^zLjlV0mc+a| zC_(Bmib&KD20((p{U!aM%&7ak*t+b=$qUGuM`K!~IxOFGI^f>)bMmaI7)}TMBz3vo zR{#)zzS4xw0bQznKJs%GGyh+drtjGN>jU1U-O%U!3?X`wG50=-;=ZH&QS4+k`(?8| zfLbbnCS(#bQykuA?rHcmp?5 z-#FPQ*W5U*Q=gL>Rix2K4Yo>d`@XT>+~PrGQv?zfK2q^<9i|H?`Z?9qH}y)Vzin=X zO}TdlpmBIBCy)i|8%E5#?pSYm6r3poEx2o4}+>J1aAfOz^UyhmWr^7OCZD@-z8u%vzBSjEd`z-`VG^XpvkK2f8%AE<@bN zw?@dVNKE_NNGK>A7h9g1o%B{Uimergl8Uk1)s%o&IxO8{GEcf}4*ij2*wo)6<52*~ z+$tqs`MH|^KjPI_!CoWv%YP_pyd>^UZ)2UQwwEnQn1&;)QBE{>N1DVu-^D|NB?nzcUF9Cs zJpB*hgIk@uS7{D@|GIe&AsUlMOv*REEDPzHX(sXeBorZ7_<=#hBO>VB(I80t_AIox z0`W(gusVm{q@)jKeHP~ixBELL>*f?k7Y|7& zzB4(?Xjr~5^B50O0A%DfPBUz;?pLiDc;l1AzGM6I;d0m_Av4MUstSvM=Xazp- z)%Z8~j(sQ0@1Ik7%Nu=B8>oG28S=3Ks-G^YepRH=|0p#%d`807aYGxPyJ=&P&#xpq zu{1kE-2*cw=~_mcxNV>z%|U$O?JB|0ciGHg*pucaS1lL1tk8kOKEhACE3uD$pMUmX z=I$uOJT9|tW)2}uR6%Xojs>UwJmjTP%Ut>quO6%b7S&S9NsRX}n65CrmwI+|=Y-hZ>5-IuQ-1Ul? zPM6UUZ)zr8-g5A0`Nt?yhq=wdwq{bE;P;i$BScm(L4RxN4Da+h989>N%FNw8o!|U# zw&&EjY0v05D3vx=7hTyjzf{gdl;7Dlq~7i0sd5lg>e;@^G$ALdt1_QI%V=ZRr%6YK2$;cH5D|eK`4Bj90rkANf6*7tE%u$}qjS zE5VCEfQ89^l8MDEEa3v+UveLP0DP!R#vMZ+P7NI5&SeKw3-$B58T~}HkXmxci*|)! zEsx1ZbC(g^3@DVXForH?WH~c|@mnJx&LXKBH#AnWATicw@K22G1{~Y280JhXU{M02 z9|6|=9y8gYDvYTId<`)4{ z_~of9w7r_704wjUOjrk9^T4_*+dF99<|07wdO=upSqAuYHZ>}up7dhpq40NHI72M# zsavdCFYyv}S&6(%X41X=fRCW=2|P>B#23AA^{dZNv7mQ^F!oWPf z&8Mkgy5Ca3DCuf=wf>G4pEN7-*9Xg$($8331@TC`7`rKJ`QZqw)<_XbXDQ7SbuQE=5oDf zq~&=5oxTC+w0HTeE4eWEHU@E>^-^uiZHw<`#LTrg1ScyVPu=Lgul_JO51$pI$%}Q9 zsx8#W?zlyp1o4r~R0^NJ&uQF9Zn4a#9qHy&FHKx>k$Z4H?Eg}$N(G~Ylk2sRi^#SG z053S1k2u!mRNTD*ImVLV>MKOg_N@{R?Du&k4Fu8fiYN3>qcashx>Meb?<%8P*L419 zVtfX!B;MUf#%ci$-58;%knqwR8sg)&Wp9GlF{bz37*%Q+6@lRnw`wMs*AHs z8%CEGYkzu6#dD;qqiBx9*@G&DlCfI8@BY(SNMpmQAe;GX^*S2{JK+%(R9`_V==mSa zog4HvR--1uBlmeVrM%>li&rf>0Q*Hju0xr`v;*gVUma#%>`6+;sT~MB*m^X zupUGAVcj}5)!rW2enudJO%Ln3R97Y>0?h_vC-N3!xuT#`7j)I)SQ7)21oqP&TeJY~ z9Vgaj_mcRQM<%0VbxjUk?tVTK=JKj+%nG>2T;A(9L!a|9ZD^a4t;c{d0i#85U>Ist zZP1WDfq>q~&>F&L2*N~9xkR-?~)j}@3qi4NQ zuCNZhMp*Ei3(UBEeD%t^t1|jPjzZG8xC$W*1mL)e?j5t)Nu*(}Uv3v)r@hMIBH0SE zS$RT1zso=X71I^Vgd(5*6ny8sS+(Up+d1mRnW;;pr*^@HexsJ&_SmHX2N5jwHfmU78M z_T;A3=8N1j1$uK9Z%5=745%`n&%;wEhg|?zO@hX8ZEe7BbKB#VC~t3(OvFejU9#0^ zstq-h_PVcvjrmwl7W-t&m6Kc5d22p8Rt@>m61_p!c2tD=^oUuGY5rW|MD1wCnhzk- z7DSW8@E+#R&rIdoBzG&Js}y9qDz77pB(Y;pEzDnK>M^W6QZkg!`!&>y2mYetnq|l2 z@IBx=si`mb6Q8y7)z3>Q)#mZRA=fUC2=ofXi{;IYDzT6h$vrKv7#S~Zin;94?ToBN za#c>u(;$2%Wn2Cto`0sKp4?M@+cUl0zcC8L^2v=2qZR3ax%*P_p2%3sPF^QO!~wQKsQZ7)1lQB4 zcZ6*&>x-hyOF}dlrKLn^d50zi#1^uXJxM{3Fe15ga^%*Xq}hC~NrmWhS6QSjuc9Id zt9IPcCc4=a%IU6sxra}Vjoa5PEO6nyW=R) zf3o22#0 z2C&Y>n@VdvY|mIsc~n28Drn)Hu_4W@ZOfCh0_Vf0_A*01@P~ zap6GR@UuBIwxhe50s*A_J1Rv4COA`=58T$`pD_3RJS=klw?S}TjuQK=J!|H+mivQQ zm|miFm42@e#_z!5f^#4;7v|7hbjBj&dFh~J3JY=UuI2Pu0A-u)FMR5UNC?TiIC-R! z^I7Ep)epyz6f&`Gz);*fXIZl%!!J&f#_#R&c_i$)xa(H+Jl^T4=?i80qyYxSvn`Q5 zYd)`^>{*zJ%dh>Y;@-OsEJt)0uih;)5TI8DIC`q*9-CRq;q6rBHymrL8X>lqTj^RN zI~tEM@N%3!1P<`<&4E_Fj%u$ZRBz!h87hgkEw!nzRuKIxe5Yut+_)!qGeFz=>hjxt zGCaU=#$iga;U?7ABW{VWEqOfnZV+IY715f>jKl5=X+G;qXm@&Z?g^(IQab0t25(ye zG-M(qRQ_slyPXLl#^S$%kIeUpA-PMpJPk;-$Mkzkxc=~lROB*PnFh|lF;lvk4-`cp z0d#vL>_%r6=p%-9TQT>;=y#MdafNUIt zGhlO0^WLDY;y-cwatd9GtnwI9i5GYS2**P*VZ0KJWpT;xspAqms+--xbu>FC@Xu1L zgInp5MB8VBu;?Z;Df3tQgD}YQKM&s}6e_jLBo09;z;p-In7!%OX2Y=NJbMAS5YuaZ zuL+WrCB1po((41&puPIp`@SdbxR8Z&F0ZD3Go1fmkPzwq%>Ap}v%eOD*f;f_9m#DL z_=nHm|8}GSE|AYx&(i;RYWdAWY)O}>mG;PpUqJzikZF6LaCC#=4gm?QD?@pwJGYsW zw}RuK^nsayE`~pHGYz_`Q&|*#W@tMvLjkhA#QZslNss%7_V~3WOpeg#I}*G))`(kx zUER`HHESsQeOjQo`@}^Db8*ZVKrz>Rx`a34EM#A!aFI-|m-FI1n!sg4pk(DiR0yLb z$b=jC1cTXeLbtJrwym+d|4{p@>wl`8z-;vBK|21O#7e?oc_7y{l-KYojn2kRjLNRPCXu;Kk(1P~2_um_T8C;$7tjS*yuXDXSApio& z0$`pej41Dg2`4woks8+&hD`0V@dhT7_fned=d#!MGvyFJ0QQ+>^cQ8uzGQT{-WK0l zlf4&e7uq;v3b%k!T%m45i)0E1egtj=ZIX*2*O-3rSxwc{O>V8 zL0HXG9wynofo3I7{rnzE+o6>1@p^OA$W0*$&i;cNkxO>d2Fi8 z8FTu`w)fvPE8icZDHOVc@S$^N>JyZuK{5|L{eA+99e9j*gj|rjR~v;CrvRmznpQ*D zEs5AFb(d$bQcmg&H8Q9SnBnry5@Nc<`=ha%)wN#T5*`?$tcDhJ+VIV|| zC0IMBf(y6hH5l-9_9Btcr5!d5rLV>|=_F5X8 zk89E+HkcpU)*Iw@aiaIL>EuBRt1BQKjpXJO?P$POVS71R&~51eoxE^@53->at(31r(`(+>C$VpI^z_Wplhjuuj#T zJ6ge0eT(&VE;)kdM}-MZa>!Dzj&xtYf|$jts>&A?T3k0E5`H@C!8r-KxSDdN1w{Wv zqukjmNU>GHqmE?}$Qk_G9zf+!VbsUI^BHgzj_2s6`jg=+4k+o+ypN8FD9!pYO#^ysFF@D{BJqDv}D8XpebaI9gqi_$s6 zmQxgoWtvXY~)BJ&zoe=N3VHNP-e)?fCYOu)yoZIWd zBg@!dMOi@-%h8ChwmkVx;n!Q8CsSQ{A8aLI#!gy$4+n0?&IzKx3o;=I@HW^g(>ObD z`>CJqm;hvOoo5tYQ&T0c1%@A{iUE)#N+JhYeiK5m7)>yzBJQ>K1mIC$=HKo98O)SB zvskVmOmVpFybg;@zk-$cO2AZ~wl1%F;n^wtt@h9u9C6=8Co!KvEJFm+wiZ((xM!k5 zJ_XD1G^vGQ_hOsG4;s<>UHik#0z{024GS$8&k-nhE#*kJGA9iiLhjN(+rB-^&kb*n z%v0ovLo7lr+9On@V{vgX(8Sf=CEfaon?g{v>G_qNhsLF1ztlY|ys;0_e;ntsOL5>sQs$t> zZ_g7!TAXm^828+d>*yYAYW1eUNYEQx`}9a0sU3@$?b!)&kk=3PYRnm{@<4^a18}+5 z1u^TcGYh5FciYN^CN3%+L9Pb*I)=!7nUBk(U?Hg4LCc^p zRk?=nwECx)?eNO>WM5W|8J5m-mY=%3#XDh5YMD1ad{r#Kd|bpwO0MR*h4C`h5fj(Z?Vp{=+#DZSTA(F z^AvtqU9Gx6inW=MeR+)|6sHIMco4<5&U~X(j zd{5G$IwT9zuM1?U^~WW0+WmgT@zoG!X4}7)#{6C8H*=pqKV+YA6cLBIPbvO+$K_rP z+Jp>J@He|=cjqc&ojI{1j=!tUoTOsS9f2cdgVh=RTXi_%Tyq6kmQ5H~Vrj8rmZo?Qt=5|Wu)GL%X^C^9x&$y_27ZljW^ z5}BvC#>hPT@Avh5e(Sr||Ey&t-1qyOefHVIYwxo+kQMEu3{pGi@m{i5`cbIt?5@~@ zz#@M4m&b8vMOAa?all<4HQS+i7V77vX`e4mt5TF1^NM{FPpywM9@`8FmoBfS?(eHL zspD$7|L0Bjhr`aU#nRib_n^Z459$ctE1~%z$Hg;vtGjo0nD4~DdcbFSig&12Z8zzC zjnZmS=0~VvTfn&!RX(z<9=2mZrRj!H3yy+4;8%Q$7^O3sD98;G+R?kdJz+k%yd~oL z@cfCN9dW`I>?o^C@M`1Jw7>E^_mya!Z83=2e-sMR?V8gZ@{ITXo((l-0rS0=S)}FsR#r9$O6Bp8;6OxI7Gom$b#$reah5*U>liVKLRcNS zr>mqZ6H5@g<3J8m!nEKl8MvlA8X_o3k3%4`zQ0Y^>M;o1bXddl$VZj472o$8c=GEt zH~FZ1dg1^zwJfeSehpoU|9(==s`XwF2}L%tKD>7JR^PR#rml@Ys>+*Pd4d-d<7 z+y*X@!xL?vCRd!5)8B3_C}Q9BpSiG=6X$XqYb94}TgNCyh>R4C{7t)Vw)@#0>Y>?N zG0tp(!!ZH(j02sXk=08-D~$r&yzpY1l<$5^x|_Ip!Ln-_&aIlSGq&oT<1hOLDqc!6 zijF?&qqJHFD(*m3t@!1VXvI-2^I5S17in6s8kB4$4@XBQ%NS~JU3zjr87iuvnHoU@ znJ}A#sui2ueG_UOb3#KVB|nO9@z9e_5p~;HMW^Ghv3aIsSeoxGeDF42Co=wf&<`m>T5XVPLAtrM3sh=Tex)82*rg%my49YlUsm0Ez(9X|oN8$KuY5_5uH3j#g z3a&q$y!}4!`6`CmKA}}-Db;@dEzLEAin*R&{jC4; zh23Ne`4{S}?@wikl~l+^|GJC?oEO@oB62UzAP45WteYS?!5OQ^yHw%to1$#V1kztVBs zJb22)jMK-_OLR??_(W!2R9H8y`_`IXDa4Wm%&iLcul&hCy1Y5esJ+5B}IytjSRJCDVZ0QL21IF1hu5De*g- zpuDoP`HAlPE5=s@$DmD)a*-1YNoQ$##<4a1AGQ#gQ0wnE^EDY8UzfE0VQh3f!Rcj{ z@qan-{(qd9VrQeuaycsl(ocV<_?hS$9@r&OZmgkm`YdM-+>R>T8itQt`?U0~PqjfQ zXq^EyWpQ3?mDaM$Rm0}!J}I`aLKGfj2QV|sqE1P`vnFkpeHZJbuSvrs;fFcwF&iLi z+S*@cm*pul_7wuZp~BSK&D%U=Uh(C)r9CrPMdQ5QPj-?6E9z%a;QIG7?$CWO`Eo~b zUA~#+lc}lS7y7yr|05%*M7Xbco@$Zr7u`?uKerueWCdHJ&E++hM%3PIM=BtX zETnW?tMTdir|$gHN*GNLS!wy~ETM5s!3`m^7wb`G=d4d3qG)P5F-Wx4C~;`AS{80& zgO*GH(^ma zy8Mg0m{s;_aS!dyVNgaWF`|-dQM+&?d~6_PJe85;QB!*--Z-G^@At((C$)$S&xXZh zf+VUCY%G)Op@oYn{b~E8HHb;g>oafMldaM>5H)gkXOlf*fpW|!F`=?7pdtIYaJVWV zIsKqH{48Tzdep;X+QU8zQ2oL>TtxC6hgKURM+7;{r4lkRtcdz~W0Dqbf2y7-9p@b! zc`;>w_Xj4Hskt+6v!hnKLMroTE$-~y4HYGzt4J6veQa% zrs1jzsGSn`n;Ad=J}`J(KTJI-wG zaC)Ad7n)HWq07*8lAtX4PIGZ}JS-iD?)BtzI(da)I+7Y~|1`dS=`9t$pR_O#&#w)d zuOr&fcCcnl{IcOflZyCLFRE`tfu6vrGMTl!?>_?g@HB2U9kdf_hjlz(^_CJ}POoLL z%JJTO5sxnl@*^HcfdkU|S=Hw1H|J^}NS|ciA>G?2M&aRJ`^2)iINIh?#dU3A#YvZk zGiY^v)$VMd;GMPST-xii{UFrst+81l>P)A#2n*H#C@}(xm0W0jZk>BPk@#4mE?kMG z0GcPeZWClLPUc8odzfC@Om07x_6W}L{IG({y^CN7A%{4huGRk9 z*%~&l@FY0YDbv+AmIn;S@}QmJn`p&4rTaIZr^o;GOr6DL4VEwz;_+Vg^+#8WI-GI} z<*SuwInjwfYx5Hr8<&Zl4;&sC-RH&)#rSry-ZT{BT!}R}X%xhMzN(=LWYlHd;X*F5 zqAj(1Bz3L7<)N!xd+?^CRU?ewnC-Ax!OCoHxLHbaG7wcS#d5#-C5uSL{lf0vV)5}i zRO^&H-Lsi`V$~uZ-k;Xi9nM<6*opg59Ot7J!=`!(QBx`|snU0iO>M4dzw~$Le+#;@{rCPL!{ffuepQeJ%{u42XDgZ=)>Ke$KPkQ{lM~SF$vEp(q>uN(_~5 z`r-=VTyv)bp*2d~+8@qOS*#zV(y|sQ?6a-IZG)f7r40qeL)HFzo*#7&^ZDA~%6?*^ zEt9ii)e{N@!)9`@sjo|{0Rm7_1cVc0Gs#eo3(&b?_K9`9{Q+u}`VMm3Jpg_3~)T{GzTf7f`LKL^EJQ6p) zS}O9WHY9Yoqe33Dam#j%>C&wSeq)^ol{5DF)4VB?hRanIRYPEkb1hd&_I8K#EKbLN z?{eYtdAwywVg#3)YfMI5r$iGqb?boIm#CswotW>e$dxW zIrbq`FmkOXh&l0^Q_E#w+xX?E3FtK`SVA{Ta`tNlr62upnfJF%*Ew*o0CUYMYngG< zw$<-ZB>()`5mXNR`+({N7x>b)&J?tLRZ6yErkLY~l8Vw#pj}h}$)mH%I1I zxc=7ihOL~_sOQ@f%ja#@mPhVa0l}Kt*!g`kR}l3>g?P|$&aYS!6=_d*!ov#uDW42X zIxs>*MAdJsJ6e2pIv$|59Q`oBId%BcnG3*i8rL$yeg@QA-Qz=X+wcQco&jB}pK5N7 zDAH6GX-2VIK=o(N-4|`D10^b(@^lL*MhCzfEQ2($DMDrJmK$HGSekWQf{)bO0vq$_ z9qf->#&vtn4ruKsM20j&%`PT_4){UwI;*Y^gd9b5<8nj!5$Q(PyrU`R97kZ!Aw=_n zGD4igFQ?o_?p||#w&=HXu0Ftzp&BwH*^V@>H{YYNBfV3@y)n^`L+7G=7X0B{8wL)} zJ!Wh)(LKf}z*<{p-y$y{Bi$6|Cn&`^nOpCrJFITk-T||12C+3xPYQv+81JFl#%mr3 znYe`Q)gpF;>;EmP&YlCRlFs72zA0#hLcwv(2G-n!JCI}UX0P)hRNK~y7n>x@sd zmA{Sr2ghG5`{Z&fC+*n}X!vndceo0~$|VmGMY>8rk&Zn?t#*r3i1cpkfRmYXX?33X z3n89O4Jd^JM>xuRl?PA5pW>UZ(if|c??5^hM+9bCU?E14kAk`JfmH1D5q=~&V(w9{ zIL+2ur9@oniGZAC;`fGA0fh;{qOkmsomcB8JigR3l4_R91P6Alg-7rm{bH<|KJr#o z996g{zYy~DTzG%Q>hGO^w*b@_+20P$t=N)q7`nZ{F!+P%RFV3$}8}^w>!Ad3P z(c!2e%tWOomRx%YGbfTC--z+&!Qbar+(Pn~Q&(sorUBGQ%n1GTMOJP#&Z$Tqmsk!p zT06U;@~KVk+ueqMzIo&-?Fo=3NCsPuT%qryNrRdDymgeM|+37NjeM z{%TkxS@NtdN1h0Pz^>ztNp)b%U%;3(b>r1-qtt@SSW5^PRNXgzj%Vkj5Q_WPrz<~r zKl6KkLA-+7mq>Q*(`<9r4272mtd)fgL>_gdt75`&6On*}TLj7p?u(wneUic#%zz)E z2(e;vAF_h)TprG)J(vbNlz25%+M;gtY4xfKP?u#Ve!zaJxO}btxnDD?)eLYk9SnXF zUuh`TY4!5$`Gl=A&vrM3W6smT4DL{;jJ2uMpY~`?d+SEPPvtgJk1Q#81Ttoq)mD~0 z{ts@&M4!CH#7PanlW*OKj)~qMqTc-^-oC8zN`k7KBt$R5W1+xjO>waHFL%thri$u& zlO^GsBBSY!srk8JYG0~F<%btyP`%LPs!4aw#KdRdN%^HR=jD1@xbkPEbc12Cz%H`X zByT9~nYUy+cC43N0v1Fx0eFTnNwgUGNZqo1J4z&I7I11JWs_dDG&Arp=gshBeF?@H z;l4w{B__HJhfcoJ#W?Siw?%%7$Y}*(6?(5_lDMdX1_Xr2=e{Zl&K7-jHwz-lNZNy^ zn%3DHAOu+}&~Y%m`_j`-Rm$~B69&I6QBO`yaID(IQnB0S!SlK;jO)Q4!r-F6+K=M8 zUYOq@V`&@S`e~E#qdn3gn~q2e4Y{si+XKsYC^zGayyv5KXn_y#RlsDw2e}<2P*17< zW|H@4-$W#HGpeG*f1^u}i`l^nmVKI@GhbyDdoNSJ^vMZ=y&bdR1r5uN&e+3I zL+^TvDF@c)W`j@k_8zlE%DMR~2L9AHB+RcEY8%YuDvzx4HWCQEDzc=?ciooItz$ zYG%xhLqxAQzCEBvsh0pj1aDiy{?W;0jSM~>FuIUe_ zc`af7C#p5pjt}_#x_&fVG5MYBGgjsd{#{!fE@eo5o&Vh#HPtucH16BeRbs3wU0zZ) z-1eyqQ^*j7bhj-t;b0lG<3xpCO0*ZxFE{7{HUx1`(oW$%Vc{BC+QgEEPS4j8TS$zH zoe7v+yd{juOQNjnYMLW_vmhbInh|z0ItS2)LN%U*mh!Em^?{JtFWa|3w%6lp8S_3W_ToLdX@e;W50Y0J z(IugVNoST3iyp9scoM3P_x??L8h@d@>-8KLYk1-7 zQPf$iuhhC~8FYZ~0jwJ^z5Eq1hD91?OLfe|mw72gssMjxg3ur@KMP6ZMjYGlJXaex z2<5H1A}L%Vf@!pz0y;O!DSlSsmxQZw)O~W@BG9gCfax~luauY@?p4lof@yyU01(!D zeJVeYZYN5_M31U5b=KaFl4zjREBuXM&Umj;Uh?ea*7oI6qDb{IeYvZ!y_0>oq>BK7y#HXM~2F)B1yQmPpmBlC{NvEWUq^ z^Sx4DqI6Phbsy!W8PKT?8bFkDwXtFkCG*+ZIP2`u-i&@-<2GX@2f-G0nMQ`D^zKcXVe!T4kHe0GN#Zm)uVQI`C*Pywb%~flp0ry5v->Ie=j%_o zIyR<5feV;~E!VPei2ve~AvC$CJ{3t?vmcpQjF}PYx)}57=x-DV9K0kLCRTQ%x(64ri7Gsh&B zTeh?;G7D}ahZ6g|sN2S@KRScgTE*2h=@;u8^sTtgnZ3s%03#9FIabpr^KbEmQiMjD zJ?A=}RY2Vu4VC~@=udeLIZR7xkMEnOY$C)%k(EpLjRRASm*j=PF@2|uX(B35#b*G| z0lw2nFc5c`Yh#SPf@ymJ1K)U`dk4VLV8MGm_M7J^R*yrZE7vmA+gg>YeyO)CekAoW zWk_qZ$U6@ioO{=uU9t0g?T>9M#-TgYA(H6?3J->5hI>rS@fy*DlOLH;vo<#yQt7a- z4|wG%&P+Z!nk>=aUC$HpzUTQ4*N2xuA>n&>Z$^^u+bbKEUQ}g*wn3Q#PP$(zW2(h` z82kqPduopaWYXKkXQ(~hQLvc0C_ojAcRizSZXN%x`;=He(DHBU{QPe@+E)x#8C^!C zb2ViLXtskV9TKd}b}ra&(XA8ibKC7OW{-gbyKaZ=CqlP$*L?TnA2%Fs5)TYK8ZJ)> zgC*=?!hA*uQVlC?3qEux8o$e|YY&`=mb-zB)W^GKH&c)iwA{jN-;ge*noL{k1 zui5awa$lBva6>l-jGS>~xveA2DAs}{U@Th@72k-K87oNPa99)~X_hqq+{%G+f13;~ z&SU3iAKr`d1ZJjjK==`-MwsVY-n)|uup^s(r@WF#KezrZjk`zM*4#dk4l!%&STbsw zuCZtz+k$rLvn_?^4YW3eFg8Xgzh528`4K#GWp-fi@Q*h4x4$93Hn|Nun80pMh}fV>0D>VjaP{qh|v_+-X~B?E#9 zFEK3h9sDoGVtG2Ejd74%)i|NQZ0QvO*y)Q@$i%1F?#Zq+K=%#^=SO68w`^=)X$1*Ww5n)=z& zGdLa3NM`gW&(@~WKr96MEx;X~qaTiHcZdtYM2udC?iu+C-c*)7Sj-6oKqJ1nacL;* zgPt66>$iS=KMeSpT1f1NfPuXQtGvu;$I$!=pk)oMzZ`lhaYK=YC0M+=QLb%B#P=+L-Uf|TA{ce1^g)%$#|cBerLx|V3JW?LRPj13gFxE zUfb~Zk6;liv?eKBKO=Wgrl=48vopI}!fN{bvclYB!P`MwGkCvmO8 z22+eocMT*Q%!|HVsWJ-`G1i4((Dy4y7j)Nffx3f*y?2oUF6)T4t>Q%Y*Q_8BxP$+5 z0ru}}wye+pw(qKY&gbK4bG^W_T0h2c=)IRcGg*3wQNm9`aN%R{ZtKH{@Y7B`qaV7i zIpCiQ;$88MTy!Id@u+svv3cE2!Z&F3Tgd=&S@M{A^`pVi#Mo7PS8~BKRjuAj4Aro^ zacu?a$2Kc5lf|w_a~COA0wne3aPvMM@)lFg1tpGu{-aD8*lGvaA(i4&4<93RV~z9K zd50Z2Y{kEVraW=#GeY-?JV8$K?-?{GHN@NZ#VDRo0hV(pIy%O?Zip4rp(JP5y`?94 zdG9I#f(O;POc!qk_0p&n@v0XR=8v#tFd@sw(fgAwO-SSi43BoC0nkS+-_<%lZ}wO< z9rU_n8f0!`Y4_MD`sum-+Hk9WVzVGJVtIm#-T|8pz$5v< zAEqgIy5HhZU(j*|n^r;|3X$1#=)YmG2ZKRj!Hr%1kOn-mS`tv-) z`GJ&UkXZl{K#|1db=S(4x1R!!ovJEp8Xdr`pFjpinQR{+fl_xwb1_H zp&h^c-8F%WL=xbvLFB5-Em7wx^u$7V@Jhm4)S1)nkE#`8kN6eMJ<&@@;>EUY+9K%A zjUbyI&84l`^E)iY{}(UJI?^2rlyggN$7%QqKT>%W>O8RiT?b?(D`S!HO3EgHK4dwt zf((dSYimQ)`RB=YM9}WGJukQoI_rt-MI9#HWUF@+^=w}5k_4ZwR~l$CW_}gj0v{4s z>9Fu^42H>%$81&--xAz-HzNG7uW!An2*)<5M#l)(=SXI{z`N$dc!@xfSW81mD_ZOYhhMB4Vi07Gy9l+J<0blxR$O}E-5sG zAsFTxaZ!NxL%NELZvHlv-HQrAU{;R8G3>nCTZtww?j_06g5!(>9|r5))Z|5yN79-vIijU)n8}r_yJa z<2^q6=KH^bdOZY05)tlu6Lxz%cp=D!B8B%-YTCCQslPKC0Nc>~go%f}h( zJ(oiyuhwISQ`~v0xcekz9u3>U^Qh;C4(bnog!4zr-{M^l{+$y5-k&VG-Vdgz-{Xy! zyLXrtOu$=kD{cn|ueLif>Um&nflo6!Dj z6C;vPGzNEwgJz5TpJ4cus)@SjVB=Lp#QOn^B1VB|C5Tl)=u<$6hZ3g1f1oN=QWGxb z$P7WR7I>P*?94-A3C9jlMTtEEh)Mw$0Cw>B;#m;5Au@(5ICN4kEOFKd?V54GzU#T{pI+Se zQ8R(qC58j0CkAm*5f;zLh010q%T(R3KW}zD?yXDE>U7$AnvrX1y1Q>%^tQ=27QOSE z=l?goR3Sse|Qjr1}#1b&!#B32A( z{Ss?P7I`$d#{djeI%sA`2`ORaR%AiHUdC1O{H(pz9ba)Lymp|{69rn4pK1kbl7+xV z73XBE9C^c~`RdDtJ@Kr3ratO!DrfOG29#BEcYuXlEKck4*~?01LSoxMqfBP1HCu=;!@rZP=z2j^qow4Fbim z=1b&w5EonSTdut-HV<1wgjeh^)tA9H5MWuqXr?<{X6E`&kn^t#+9~3g4*Okd!ALr8 zSO+QdmN_}rSO6(1Fb`$fX{QXN7}ox~k}6{}efp;%X1h8*yQuyec#(r3;PA{xci4Nl zdmranY04!RaKNx(0v@*#ZGLt7JmKpzzrBdcxJzs&%I!=O5A zJac5wMZ-BuM0HVQWu$88vIXi6QvzRT29DDIj%*bZZF9vn_iaQP9D4FFw4-&!+1z6y zUkPXiTv8P81%lGyTm`5q>Uusaz#N`G`z#Ej7XF#v48=2Dq$xAPlo_8E`B(Hfme2R% zTem2TLyRf0uIsm`F18CcbQ)2$0lhnKbEF9>L4O8s%>Xb3r)XBhxnH;*@;raWZ$7x6 zP8XC2P54v|J#+sNX*(g7Du!*=nIR7xiSzI(mhjm8xOp)%h)$RrCK7*>h0^ppCD1Bh zhAegO`#okIJJl)iYwg8#zAf^Qmq6&5Vhg!A{c&u|uFS_P`sFV-uy{RvZU4|ZHd zN?f%>(WD<74#Ae^!Z@l9Zyy-chqryEiuy}$kVR5cyQA&LJ3iZ)pd>hHzjpb&#u0vC zG6lqQ?nmE`&Rv*^NHtBdy6%vtvfL|#8v=?*{m_-IfcKpDLj#gx&VI~73=f_Bxb ziN;HS+b7eZRHYF}D4Uy_l`9nDi7ZMmE;pjzRYjtMHCGE+41MZfZ`zDjm>n;o!T<>c zUC6A3_?2p{1jVuLO&q5DpgPX)LzGLdM|rkBeT?%8665-?Es$qotPGm?FlDAY-yZ|-^2-2$?a z8aV8XBkcx|M}k=&Is|yOB-|SzVLnaVw}}#xDg4PEbV6IxbUDc5Xz242G?QaixwY_x zw+VDT8m8rYT!2Wy{L$e)XespPe=?9SIJ+k;yQ>ES6CvE&#iIu%|8Jx^73W$GeX=U=eg}ng@GIc4y8k0=W+;0y-OI@d>0Z+ zbcY`=z1VVf1E_;SaZwK|Ul!gRJ2C>>M_{|02Z8TImjO_g;eMx4$IqC&zKKuqScxEE6J zY-r&o*$gEL&|DGWzU%MY7$dqjj!0g@eYt&_!@1SB7cm~{$0CsPBvfed6k5#|?o>#) zhhYT3G28GKVw6~O=MdbGMeOPk>Ze`*ml_bdsdXmwEP~nR=G9nytJj%63B3rvAGt9) zdr%MevM3@2mMjSwBP!d{VC^fnuY6+?un{l{jpov2N@z;7V_Yd<&G*=MbTbyRA5L3^ z3)UZfDbCGsdK^Rr=ff61#R{X{BQH$kq3P%v!2FO>(OO)0u0{T}NzRcL`2ojefTcDy z{myky_K?_sQJ8G4nVO#*VmPP6Os;{}ifhj*+Cb@WV;Ps926&Z5Gb}dT8_Orr`@_FG zSlQ`ZzUwnr59ln3DJ1Nf)dD(yBW(*{oIfADRw~mwf)b-SIR1WJzPnnz;>WdFqbjtP z2Pc~I#&q={WhQJR&!elj&NPxKO}~ebA%9qmVv@5lmrTg=fk;6FN&gK5ewlI@sF!y0 zcX6Qp;qEVQ%unmgWer-;DMC)#A0j4rV5dmiw{^@P;d@(z&i}i10%p-^Zrz`XrFZHX zXm>n#{x{}*^~y1>O&VcSf8OT_O&3}YMD{~CqT8-ryG8y!ok5CT|M4t!Po8==PmSd} zuf13?{|NjD(6eHtFE{0WF{rmP5l2nqFXtE_tiIc745 z2h=Z~(sG5fv|#gR829TCO&DYlxI%febxH-QL0gf_yN>DjIl*Z`)%~c#Z#GeyskZ({ z{C9}IV_8y}?#a{n9?fzpjXnSTN;#laK|hs=Z2UOOeG~g}8O9KNT=cG9Vx&HS;3M{A zpj9!S>?2ix=CE-OyxTWEPMf75BOatO7&R%=ch`)OsD>01#D}^ptewE5pJc|L8cgdp zvrjID2&fF#te7+@9Zt;u<(Ba74B(u;2x&ddaZM}?Zj5A?+JE`x(aWpa635+` zPvmQyWfr#B+UxEK*|>xXR}E#F&ek!EGB?B! zX;cNfFlxl=iWR#~KIZ#zzgEQvKtb#;Yjnltul}&SqL7B|&l_6>n?55k93h&U1BV)O z%zYF9g`=jstD=gwpb-TXB~&rpo7By!*I?es#*bI;9G{4Py_y;WU;Xg4&*4qe#Av8M zVQ4}6v>ILHkoOkh$!2YhoJ`2$OReg10wNvajADUnl2RKs29h??EhFZ{?NFS!x&%}5 zR|^qGoL}w+g<=!TG0`o%5#9djWaSGq6XO~xNDPU>|8>${34B%M;|pJx;cN19_fpd+ zBg;`LEA|=W?ks}bFFxXBLk~`SGNGH*Ikg=n_NXx(O4VwF8J!O7TcjTd)Of?Gp>wG9 zUfC`9lyqj*cCt`wme7znG;LP6f9u}d^Rg?bF}vBP45$$Pf*%i=FEm)@^8Z=#Izi2H zeZAAgnxr_g{F4lu2cx-e`keVQdB)Udis+CQHxb>8X}Exf(rGd?17GT}o82&+Om)k6 z4C*$$`J(rJ1c;yZ@O7FDmmv9}(sN<#_Y%IX{$&>2P?bY^B(r9e7A)PE%eVa^0k5t@ z-+YVzHa}gVzWm{q_eH%G?AXVsD8+`!e9YJ7f`l^CvvCpd4H5x4qF7>%8ehx)SD2p+ zj&l?3f?&ekv{cu@Okvmxm87-pDDtd=Z2B%`W<-5#D`zb#D~Idh&=ups|82Z@>vHig+`M{eGwptdVv%_9lx(>-}8sf-t2kg%D2>95o<(G^p8_sr@_OVT-qLqMqh;gdMh=i4$qKQ=*2o<2=099g|_ zDfrgDx-0&d>BlWLnyX>oG-yg20NkCz-umVYE_GcJ$`>0cQqMPc0a3q$I!;s66q;7x>bH)}Hu8@W5^G z!I<(dULs z8F^=dalUY0>c^+9QAq($KEr1no(M9N5B{ptTb_XY_X&G_prTjnMMrZUa@WB#0w{B0 zwsYh@hTMLycAU_zw+NN0!r#JhVBdLhO}2ni*4( zt-Ar7xxm;t``x`5NBYM=ayz1aw-XaXll!dG3ZBo$OWW(9a?Nhk80j=hhX<(TuvX!| zHikkLxN@w~izCTEEmi7*hx1?@-#$dfaGs5=Itg_=6yC?|_ys2((=U@zGxr#XqH3=| z9N+UuU?LZkyOnD58!qo$?TtN>!KB3 z-a|z8xkJF{x?*5j(%(Y5UhaBPJxfgWaIIeNarn{t3BgUs>BZfoYI9CVV%Z9_aX6*> z45}IDHH*n|2`m>&13#ol4bz}gQ!z-mB3BHBfjNl?HRXOacWL9tp?=pyL8KSe;ERU3 ziy``e4XSZ!iOT+j1f_;r{Q=;ig@2$H#JJr6BAvltCCQdsfegsuB1O^RRXmiH3Wj@` zuT%vkplZLsW%^tT%7O=(m!P)NZ~7;=wbu@GkGxi@pM?tf$3rL#aRZu1O+N zA}0r3x05j8910?UK1_22kF# zjE~7WI}*8k`Y-k;=I?@U9kaB?j%k+Gpy#rRB_fTN{F(DR55PVQl1I6aI+rsgY_bAPW>vf#;e#XY?Qrw(DeKG6au}bk7A;ej0SDx(F;#wD{=4h1Yh2 z4cBn~>dDfOKp>6(>#*mc!^vmQHG;~62 zK-Xh~=`sW<@oj>$rvips9bF#qgHk*sCOeODH!g5LRLNt<>?Z%bKeV8k#dpSxjfbDA zuIO1l?Y93>wAdFa?_gUy7}MVxe5(5lTwv(-*u}#4(hFn6Ui#xL?;F?XYunnO<{>ke zHbLM)!RRkDWZ>)Z(?zyXXF11Y69JcQ<_IhUD`n!#~@LTd)=OOF< zyz0>2?@&Wy`eT50htx9d-Gdckvh>;{*B3B#9@O!;!gTfsuWiG3>wE|*2+P1%@1!Hx zWVypdD7Uq}wxb9dR&6?Vj~$8DxvHWW+W)11GHucnD4?@h)8Y((a(k1?o^>;k*_ECr zg08<@a1GMzUP8HsvnRPUCVpvu*{-58SFDmO{RgJ=@qDFXS{+Y2{;$6Fo0w?#0w&U< z^Ih~+kvgTyT>}dZXmCo-)~O<_Huty4F{0kBaM5S249=%qvb2Hg z^wAKJwY|a+UYTbgkl9qL!sO1mrk_t5VTrx}St{7J)Vm@B4Hm6l+qf{$&pXg-MotI3|9#!6Tc zrTZ7$qv2R7rq}UQ14~vOthyfUhxZ!a!Em$VF7t+kSywNK7$-$#RTXgL-YKQc$b_;2e1g4+)rx9KX-qKOC9RY3VNzBRet&1a3w2!b$DmmI@02NpYeK>rT3SC;N^>E(gD4gA8Do3)XCi zaIZEuh`&UV108})+>Mt*g$FZTrCZT962}|1$eI{!1LwiF!kH6Vfcy*!!p&)}hJu-n2y-PYTI@(vxa;Ax-&907TezT{{~iEm+4R3SHPF{P_zGH$DK zoe>zTj_!zvI$d7~?Ldt?-yeo#to)`8BLbO^91^~ekdn50h;qOJ$ z7pi_%9Kra`r~ohzv;LSxo|XV=lkdD?m8bth_q87%(QT+FeO^9~fF{{oEJEIf<@E_B z)$#b^4J`h8zLizIq~Lb!VdYbo>5q$W7(g7&z8mty|JB;;HP3*|$28tEQMg?KL=S%O1O%Xd6ZQZmn)wx6>#b;64hH1z)3o_FBWm+GLr;;zexFEoDq^y_ zVv*DusIdooU-hj5N|}TPH`vB@J5PSKUay5hIe#xSZ9-{hUM79YDFnakwthV4w`!@3 zux&t$x(&&2tZ4DHtnk`Syt?*>Vhh2>IMs;%)FN~VO1plLSg;u}kc;ubo6vY$jo$MA zD+Wy{2IF3P=%nKg16dy%3J%(|eXV_P<@vWs;jYU*7RcJGYe5(2hkBl4rt6Q6oR)N( zb0~1L%Vm+9R{|233fqBOlt4JTOds9-d%h)lK}uNdQq^Iq3}O3*jxi##Q6d+DU;aCDUW)|0OT^{P|C{)V;SdG0I$muqSh|-) z7w5TcP~)lJgy2(wUkFcd*>f8R5F!zCFj@M-247@V`R$%re)VV7UzPlx@zW@EBj<_8 z)M=D{kKcrlISoom9?DPh`Qi`_Fl&`$xBsjUcQeKg0ZE){P(sqa8z9?mo^rX+GWy^X z&%|j2**|W1>*I$7zypHOcqf51qx$LALGCMGIjOH<{jF0+n!2kh1wldVE?M88!LQ$( zDtD*1<;I{sw40B3FE;TJYD!a${plRIDbG7oB8r^u< zD^BUip{3i{09V+|X++DF&9yC;165#O=v=XB1t=1v9ZXpAVstNhIzxXvr{=2@8a@JA zFGq{nQALq7Ct9ctpdT9S*CyVl<*;)x4D&)abzGX)?&d{Wj6IC^K1l9ImiL=o7fbI$ z#jO@LFJ^Sl#RE6=SB$hMBV1XOlG~`I4eEkp-%mp@UoggK-4GbOnBl^O_d5K*fng~5 zKMy(bcdZ-IC<+c>|8_JpZ0U<5*Xt;J*o6F(JZ9th z>^L?Vy+uoZ8HveGu;xsM20(%;0I!?}ZGUm`X0)eBUsQBy>h}ru;Gb#RHiQ@P>CS7o zptz(5QHWH=l)`}4|Lm7lFk?pcA6&)0KEi?=Fi1)UO?P+rCnVXs17#YE)rxwZ#0FeY zHLmc2>#YK+dq_O?XKxxGdmm@Tle?aAUC(Fr`bX~@c5p5@FDrl`g-yZ)>Fwxs)!A;I zg(wdS_7Nz(RW4c)yE6i}dO7XVIp^myDY^0a6La%e@Ivzs|yo_!f2l!A5q;m)RhDGlJ31_!o0}9e|>sB?NP<- zdxIU953D(aSINF~`&zTH8yaccGMBJ$2D^-p;U0c)b1@kx%X?;{s695l6HST30VJVi2yO7qmrI6e>9AL%NMvs@JM=0?*xLqvJE;v@%m7s?L*)*RD z%B^%1F3c*Oo#lzz3EBcbCbFJAD#CPwJGiR|lL!NYdlBKu=a2l)&9ho2y# z1|>dd+12WP_c1rv?7E%BMRc@QPpc8mv*z;_ap_Xn}#Y2~Da*Iy~32_)jhss?ZIYj3>EN zKqVv&0jqBBl&f*<=kS&Xeka^C)~!|VI>c@SRelUE-Q&$tOm{_KJGny@YDEA09B zg{xujN-%+QXu~)o<&`_C3Fa{{zJi8=K;xd_@`i>36h>630fwVWY*4f0j|!*{gbm5g z=Siu#8skCA7H5GgG^`s0OQk5bn|l;dWaey;Wr3`Z5AYizYUicxD+BjY^5-5*QLrtU z@mviBRp4lQEuAZhzoYRU<0xPYz#bZ@)&w05;3}2aiqjxRcDW3eh#xeCo*Si{M58}Y z?uq_q#>PS` zwq6oAv9LHvpn|}JK{ZOTX4iQXnX$efSg2A;HnzM0vPBBKQtKMWbv{Ug&ml_O9(dKV zBxncSQb0rco&3`oZ$Jl=F&&KZ=A+(7Q+~ybcw@_xpzmll4el=yOh`X5u7QsPfGr10{Bf|>L^=Ej6njCXfD&$HyL6Xc8! zNKUd~sY8rC50af_&(r-6l(7$>x|b`aA(+??c+?0>P(Cy0qW$K=n*|<5MHcegkv|Ec zXM-akI}j}x=FR;X9c4oI+@Njww+QSg^W`puqNw+U!5~oTbN|!olRH=ziSihGeZBS^ zhYWi=t_ZcQggb$g<{E)i*IrHSmtD_t-HbAv`ERZLSyIOJT$lNgdrO)C?cAR&r`p+)AG_A^$XPU#Bx&E`w$+f24d}5`7-x>r{KaG(SW>xKqb(KrACUkk!7k_4m)Tuaho`LJv}s3+0YKcLG8m)7 z1Fpo8#!=FQCx6wsU*)6Gzmn^CEb@3xX8{yrTe_iMiP}%iBk+MB*H7Vk9L|+9qcpdZ zcMdVm6u11opuK`s-7PzZyRt*c{Or9kYt4-hEm`P3E8^`IlarVfm$l&%0Kd~bi$kK_ z?ad%a7!~``Fxk%6urW_vT=n9OrC2W#pi_&U0#a3r$Z*j9aHW?=KhU8C&I=JP8|*^WEf&~R?FRm5gf&QqkkPq@sta!#^Q%x=M!YvSzU5ZS z*ct5QM5dry`Bxz=-an1ecNuCgXv7){4b97KJuF-)Y3ri#zg&QrXz(Gnt(M-RGf&de zhWta)-{i91SZM3GDF|OE-^z#1CiEDip0W~K6Io)zxj^NyKU};=7-!yg?_9cAko_Np zOmv@x*M=E+o0Ak|4R-)kGt}zU&m6j+lEMXo^Hb*R`Q*p79lJ1BC$~LE$3nwlADY}A ztL#Szp|f(|8~&9`sy9VWUw50Nc7fGkY`XDe(uomL0_(>!PvnLBWMm_%7#dUZzsxm) z(v)ue|9H9zs3^Ct{n8+90FnZt(kLZJ3W9&v z6g!HbY-bk#GnX*0Bbaen{5aaw{Hc}jl0;p?ZKvDVhvMk)2dZ+d;SpZ}uP7R4AShsD zTZaV8)gSktpWwt1)CZO}N~FGD&4x3NhvbjLweUZ(JT4ddZm~}A-O!6p#kVTqS1CE( z%zBrs7h+s&e5b*|T1{U|?VZEXi-=dpu`o~}m0Hj< zUCGEeZfiq(*|p{*n|D}8VJmla|8oWR?q7%df-=5um)6j*Et3YgDVPh^)7ksc7ug^l ziiD}Nq=Mkd0R%?FX zcwYXG@XaVa5J*RDD<01}hSfnf?_kMX64KfiLNiI-GKi&7vO%fms92RkKk_q8ykbo7 zTU9THH$~vMd=Hc*;8&>{ygVZ5LTnA*K5OdwDN3=5%&4e-azeaoDn2;Q?8CO+6vqWs z_gIYk>j8bi!RTcpI2^g{1KM2yU#wh_1lbrl`JpC zlv__jw5UbNU+~L6O2MB2Ig)8rN*v%l`na*B)~RH9yr}idxaY>RWD}Am0~NX|-B3;_ zswM0e$%;vexcHW-FhU+2LyTsMgn9FK?WZ&ps44rsz=47eQ)^K+s9LX-3%LynP_8?* zHo)i=K3*c-+xJqQSt3g>X-|Zri~R8{-cLkWmhyL!{@O~NNKkN3vl5n$!KP8aeSEv7(sUKQ7Hn8qkZN%d zldzWB6FfvDQLyggY#6w$tI>0ppyb4aJ`RSQ@F|FcyXc+G>u0*AKn#D^RJ2G-f(YB! z1nq;fA`THd>b!#_vbSGm@zoy`ry0Yx{YJ)?AM>})Fa8jF5=I2Tq29fvkw^dirV!Jy zw``0}_onm6$WwX5tHmp%9`2t;B}(>Q*W@aeoX5;mDwsYB1fMG>hb(NR$Ouqn2+TF7~5>4;RMdbTn(A!A*Lf;5Ef6 zTe3QUK*P$1{DG6~(F#EfsD0PkQ>zuM-^^O;=2cRaIVP6nRofsL2fzw@$24`wzf>=( z2UHjJUCST;yf?cE$J$ecwI_cs|M&jrYjq1_mviZg5|_Ak9us4m&|zNS1j}|Tlra7g;c=nxE1frxE)6uGxDS^L z(no)0jt8FUd~wb085_&t*<64BB=+79ht?%ga!R70?66U58yLS9pz3t)_U5q`xR};qgQJk^bUDmV-;}Bh$&u=iTLe=w%#3hTIn7=bL4n#9IB?!px zA4ql5H#h!n!MduiY1VgmF#GOn(S|~r##ZuFU`G>xcld?jy6C)MB=$RBSsDb|Xo!JE zLO^`p)?$=mb(`!2%R_Ijp98)sRC{8iq#x#czx5o$u@|;}pv>RjIpw}|jHRYSE^xAe z%loDF(-i`Kjk3Dbr}+Nqg6AQCYVlCwWx#rXY|>&0xfn~)MgkW-bf6&H{SR;MLeP?W z8j-tMP@$`+F!hWK-Gl)zZl(oW?Rd(YnP&eD8B1_@#3lpd_?0#bIB;`j-$2Q-@mGQG z;)APLt{;Yln0qnWi@ZPDp7y?gesjK0N51KdZzLFPc-KoFN2Sjy#MO(;w$|jgT=W>4 z=Mv(Q7po*`EF@1q&@JJrL#!?D;hm|#N|!?HTfBLOMPpLU^+f^EpK`XTl064o^-dlv zxvhAFpTzJ<#fLBTR*ejZnBKJBXF3uTv-g1ApT)Gl?+-@DA5>;hlQwPpSNwGe%AtF_ zwL3l*al^s2?iF^s&h&^Z?y=lCLL6&Z2_-TKTV<29l$Evx)aCglJ9j(F#GK7$24-I> z;;I^)3@=1gtWH+N+;Ke}`TdG2yRph^Cu#9&GwKQfUe0OLC9b{rgf-v4_3K#oR%|fQ z{Qk75A+LVN+kYmU60WoON_d3#-3G(p6&D3Sj%A*cIAT3v(4@qjwE-O3w7W=d-V zI4q=otd@^|`L#ys{NUK(!J+tN7Z-zF4b51!*+POkJG?FzTKd)!A(FX^9`sEkwHqqu z<%>XlM{9aK?kP|?5m*_yI`Qt3L73iGcAwb!wejx}7e9A&E6+%iTojoYcaM479s+qFHv4VZk$DlF4F%D)DoG8w1ewYa*>)?NzOD=>AW$P$| z>|LuFB*qSb+v=|{>DR9m4FzV3z7MD+Y;!}oRW2155n@*z$ZF1qZBpuIUNL!VRp3XT ze5CZzpV>#nH);=j>Fe4%nGYRY zs8|$K5X47h@o1xjno-(cf1N_!-oHT*Wib(tAZwezhzJ$Uk4O3g`26sNF>N^kB_9iB zf;r!bi;|-T__IcAr+13E^7ZhrOrDIr|CLT%Vie9%3faqZ7uZ2UCW09trA-EeCkDM zx&IXG`h#be1G7aJiJHF*lumJpSI`m5dWcf#i;B;`*c6<^751pVr#L)zmaJN@>E_-t zV&Lcj(N&t6ikQzKk3>q3wD&UmnOgEduB>}ljy`%q zE>v&W3Ej&!>S5my=Wcb1u25~r)Mqx8!Et(=g1I z*mOj%A?B#J03x?*Klyox*#~<3BhoIcWY?ut z%A@W+`uw@$?j(*T)D1v-Sg8L5vXZZ~sn36Wnmn&bjG(5e&Ov2V>5ZZTT7`Zb84_u> zQD3&^qgS1ETqYQAO4HQkwss-Kj+G{HMAI4!9-2v%8oP|L_NXj!zHw=G1Dd{{DcbvC8JH$J-8 z*2LA(mW;f@U51Rswr6Gwmh>N9Es-9hz4WB1;@h&Q)V?rffQb(}D4}Ku$ZA;`ODJ^z zcEYLu%vOHg2$J?>wz$W&NKl^PQ-{Di&!-m;k4{0ndqn3EE{`@A7%)aNA9HWooj5!^ z)6HjX0INKOA2cpLa(ZIx%m$zrI+$P5b>&1b?t7&QA+AeIX^3McwnHHlE;l?t|V zpNC;+TyP_mQuwSNxFZ|?PEbEa`v#1#eAF7W$sW4zNNdexOt$x_BNNifalvG1K~>U` zt(I*PpL&^zX2i6{zOPzkrg{|!$$!_I2+G}v8FSx=DZa4shFq5Z*H=qyEz$DZ^yS#t zJ4(>Oe7NF~J)Od@HG4deMtn24bInzgf8C6@aNOr7bPA*$MOX?t3OAUlSS>K&+E0fC z551;iH9#mPi5kUYQ^q(r^|jSphnd_iAW1I zKChQhBTV@+RnGC1u-K8r<(X%}9HpFVz7ycbrghZMBz6TWzDSUCny?Ky$#M!eL)c#5 zWQ}}*c3MDMKK>Ll=g&GfSk$gz%uvcREj(PT7PNaYko`(Ata(9CiFDtxtygQaYJKac zzn%+?Z6}KLPM+zEB8W{Qz)$qE<3>K4`K+P)nn0eOZFAN;<6C`N!dsucY-$vn3VL82 zc^>@duaU9^4RP=jKw(*hly4@-VUt{Lu~`@-wFP!`?D#q_j2-oVnc$aTlyuBXOJLD;?ITOHmakD?Dt&%&Wo&4L!D@??Xx<4A;`wiAYy7o z-DSXUPxHE2Qp|Y_JX2T_P#VO_$W^2u*Ip2@$uI7DVMVp?n7@$?CqN-9yKkkAsI`5F z(2xpy;TiS?b7VW?Hbr))U$TNz|H{MIENDJVv&pQJ+P}skD_pRlE50Jc6L|hbFi~CQ z#eb*SyLNNbSYQ8_X*Xn6B{J7iqX0@sB4VdN(vtI3R?<45az5F(Vm!Wv80qfYUP!d< z41*fP!bkV-#Y-&#FbB(6pG}&xo_M0iLG14aW`g^>KWQi5y{tj@Va0|{45UjSXfHXk1SyC^O_@ZVtnP{77^Nm5hb@5_Z!AI!sC zRJi0~-2}Gp9I#$D<$sUi+I~1=9Gm*i#`w7+jmk!&wZo|CuD&Dxw`Y8C zM7J99*80krdoWl>=-rTRtdcbR@9F1$k6@>@m7hViLkEj(f!0=m za_aMJ%Cpg7e0Cx854V4?KURQEHU@>J4>|tEE6krAx{hjjjh=OW>Wb=Zf-nlhG)~(^ z=&+s%`ms{f+KQ&EuQ&wtWP|IAS#|fTr)RS;mXAhSbz6DiE+r8R4f6o!--xf;S>yN| zFHXk3-Uakh^N!sU%a_GM;9J7lo&L(ev)s85^T+L5gm@I1ucOuTD?!88C}pGSUoN^lHX6)q1s2%AEao ztff#9*<4bH)n~4SoaXgt%Yldry!kuGuA}(2-MBfFP$Pp6<2F@BYo6jF()?$-tcXx& zKXs6}i9$-Mbz?zM7lZb3J#Boz;|4=x4B z#b_6&?sUR|>M_2g>05zYG+ zQ#7;n5vb!aKA+Ha!&yJ9`TQwB7+^KR(BiB7DS6ZqHn?E1RlO;j9tT6clZ77QKd^4z zOeBJKx9t0Udd`@jy>`3$AQliaonK?!-|%gkmEfV{x$`}-FXlqLJP(w|mhUe(^GlJS zoBjb=_@@kO@5?Cf-o=+^S&DraOMBqh&8H;F{@g!7e^s9d;n3^qdo9#uuqX;eX_0#} zSA1uEpm_nUMXuRWx|wpQ=TYIm2S2Ewjl8U8@COt|?wl**e6D$C&9<2I4~|uTt$lja zRYLsl@w)sgDiU*;&CP}!{`o0ZC65GNtB<34gVW9$v(XP5L>W?io<@vDTokYZ;*AZD ztCwir{|Bt~_{9>P?B=83Zu8mxGn*g8dFTTNiw|9&;{Kk-`S-ze-wE78G&4Cl;(k5A z{Nnn~kqsGJGSu0p5mPa}jkOIzeydF@m*`LyvL4)&A1pv?Md8sv?|iCwpWLK@s(D8Pr4t=M!H~#9c~b2R&i^DM^V&8t4Rg`h^93Ti z2F=d-s>UqsWsG?Vl&t(6{;B>b?MXzG>}p>9>*LRYYujbgOiaOh?BU<&7&G=nf83YG zIk_A`>aY9I`7Ror6P6L^o>-$K0@n0dzJWMmEVYMcKg>fOh%i{tQ(dUzqCni0Qx487 z@34WZra0MoIT#qF9`(j2SL`{6-AH7zZ%=I} z=FeL5bw!WxZo;@E#s}I7_cFAM?rf?vG46zQCXe#YfL za1i? zC43I&(}3R**AM35OEa^isIvm%%5piIbMJ>U4^p}xAadXGxQE0jA-uys#>4}STwIM7 z9rmQAWK4d2`C zD-4QT?LVcab6|K2hlUDPH}-NaEbNt!35sl>MclgLo(@}hShjdJ6nj`aIbSD$n+OC| ziC=0kNpMSUod=r;+}KzsF@cIL+s~o!Ah&#$0x$h|Y*MoEV-*}f>f1P-n$-QAyWJW@ z6Gn6~!lTp4H!ZfqcWo6u5-;0{97#VYKGT;Fu66je%ak4=Xv>>3RYl+KudCzb>0Iy= z>r=gf_HE^!dj?Wvakug#E=EN3MVwpew19aZ&{7+!E14PA(b^s3_J;371~)O*YNVb5 zdwjz?KHOT5J?r6;6>dyES9EG$wc+Li!zC5xAvJ&5>@kyX4PLJNLmvL~jYr?801AL# zEY+-mD=rHjXh}ixqu@d|^~?8y9(78?KiNyu*cigw@#g5qh3t092q1E|RTpG(HXTIV zhW9T;E0Ru>Y1sW;oBkFn{cuLI)r#M8>D z##H|6Kw$&ZtzjPI>%8#V^b)YYDJ z|9H8VA?>5sL%w8Lo?@E5EN<)N!NwoNv^%%Owtt(Q+TO7Ef=j#NQ}3sYD_HYj_HOKt zTOyL3&^b9{!%cVuoD8Xavl`3(^$R$wqxydDKaMerl+vO<;%7cSxcFBgCLqlg^0+rvi4HWWggtJM zBR4^fXvj!F>_C_cu+9`9UOmi!S7ouwzvesN3eu;h$I%da@1bRoHZe|0uLV6ycH4RM z;7rib%N|A+_Sd;*H(ELk=I2HH1p+KC`D*5JDe@vI*`|^FSjNpf;Pa5U$H(L8*0rwl z7cX0_i;D$v-a%7i^7+qvT?Iy)6!lWZcf8(fb#2KB8>)_ss@GOLSlVyh3-WZOO0+d3 zC^m#%CU+;aljRL9^kCk*-T-^Xs`xQ!Sqfp0n2o>Eyyl;rLjW~r{$eX-DI|CCV273sgJLzT7T>;%J>IH>D$%&;p*kXa@P@l?#c2*Pe6iDc z&_qMi7r1RCzRjhL{q0t!GjQb|7*YbP+7Kiae-PSmlfQsQ5avdIx3-4T158hCu~K&YgzP@R4?E5>yWV&XZ&Ve>aoqvd(#WfN6x< zpCIltDbGH;+q4qgiTYbq0?#9cpk?2IY8TV%)vTjPdt_f3(R(13LmXH7go}*{dDxZ1_-EE2fI;l6pd_k2 zRRq#1oZNXqW+e#L%|pNciiUkan_~?d;};oR5*@gFVoH*4`klJMIb`TbP-oqO_FdhI z9z%f6tq}p*DzWo3R+L=#@Y^xTzmlWp)V@eN9Fe$+oWNcQ)5pdX^ePqCV&rp(vG$*| zRTn^mACI>+ye)zFE(+oN?HLLC+I&)O@lH<1So^eb>0L!;1aa@({}eY>)p24LJaRFv za9;XfLL0tbkHeuYA|hfymMb*;GaTR9h={-bcg#8)6Xy4l4?0%pNxXTabJG=JUV@b8 zp(tS3VNyU?#7&1I7$sp7!$yN7Ehow>{`D$ke5Fz8m8Tvv!U7}F35sJgFL_0aRyn+i z{Yoe&NGFW+`+AkXDAHekRg#G2*~Z#GC64L0^`A0z{QON}<|592xcMhB>PCKr2z_Cf zG_wqD#b-YlF52US>$X!r{39IUBRYFz+v4C!nRD$2_ht?eNdlq4H)>9AU{=RyfXM zHseLga=J!f`U=dEJI<)y(j5e26#a)Mf0CeXJc)QXe&JcB9`Gr^qps zfOEq?e(q;?eW^2CCS)^}yk7{qp%Xk^$7RG4MT&MybK^)njiU9wHHr#KmzB8pvD7rx zPNf-{DQi8$YDzeMmMR#nA8@Inqw?P85ikXY?*cT@?l6z#dMu5nJJJttNQn-Lk5PccVMoHyi~8?fCP+Z|3UE*(@Lk z2QDA2@+}r`n${4!RJz05B+q2G#>x-)4+0xc@GbUlmB$#@p|MuUuYGF+q*547V@dk3 zE310X^OvJ@Ac%uqOLy9@Q#c{PNS{kj!Q(X4GqSY2As^>`E3;bwz<;wJlS1wvi^i`V zey8EP-kAqhCqd|(pQ&@O+Pa<7+InM8;r)j9afWgjfch$)mj7xH;=_~OPRi~mpSQn# z@>gTw1enUF0oFIp;$!u1=hssQvLdhsRLHKb&kcjiagWm!zZrG;EZ`C(?($_-!Wg+| zO8HjIDOJ_24%6yHdgvO9Y`rHd$VG1OnoV}{J_KiUg!qP9H-x?VF`5LCuXZG|e-V5N zlld#T`}gfE<$1=ke2iSnYaSx3_(vI><}-p3$FOsJ{c`2m>(JY9E-O5DRIpdK?*R~) zE5Ukt;21ue#3e8ZO&~gZYd3k~6SHG+I9J{b(sb7XDXanG?Zs>LA)qk5}ulD_I_nD|UQ^{p@_xB0QXl zB!+>~2N4miT)Td#E|{#Gxs!QfJN*Mr1;8{}>kBCyHAeK*UKbO(T!aYM(_MvQK_{$m z#P}%4JxR-_0p$mpcS}g3YH~X#k?5@9=rbn=-`}*~B}IonH}fL=4K`Hv-JanC`=0;y zF2UQ&9sMtELkj2L3zr9NraqUlE(HeRii3Z9cQh|Tfo38Pj4UU_j zx}HV+O`SI1jedK93xpMWg+iZQ}y8 zTwvd>;rud~>(2_2QW{G<;dU$at^dYlT+v+T#G~F0O1)oII!#@X$XBU)E2n|XktgF` zSAcuk#OHOlE@}BoHes2&je(I7I^sGgk!N_sB5x%!&4KU00CpntYPQf{DcR_xVZ*vrhB1t-w9v>to5$p=q3d*-<(rKSb8MEry_g*n!a}I7kxg$oE zIJFy|*7J)>$zp^RXdn*djt%CgDGr@|pM6C=>@A?KyQ*ytwCb!C;tkJ{Oq-sE@}eWGwd}uBGY(I z6AruB|Luqbc7#f0Kr~+6&|~&~E-S{ISgy_HKFJ*@h0HwLgFt+MAmC`e9p5*0J=#>t z2B3uF*_5vqpNC+&E4Y@$=cFgkowr~Tkjg^x(?KF`O#S;tV-T$Qd5^4q`78dsO3omN zi;<5q;JYu$GoIbup6B1?av(Y2oA`K6I=Bp5%7HDFb8oc>C})nrf9l^KX70^N3~nM$&6i9U=tttme7qY$-S`m`78| zuI+)l|CO{pZPY+ms*b9%GFt0@Tv{_?>)tTw+A`g<`z(hut%pmm))suq9nFt7DNtV}GMOIAj_n2NE)pz8`G*ErN;uth?C;}_uK8Cj{wp z2F{TqOpoZ4D1=R7dd`zRKLZesB#LBZq9OE*4A&2CwtQlxa1No2nt$2XMHpK4CPw7= zV{rT?Kf~emEfg3W)hEYDzdhJ<_;q6VNyGp?q!K$jRUyx#;WDx$xf&nG?{=9?t*`$V^TB4bJkaKR3U;J`^-0-Q={&V1Es4rp~a^XL;Dl<%Jv|Ye& zF9sU&eeCdS6MYYt^@m+?BwOY)(b*xPnhe`Zj1O2DzXyt-a1jwvzGK|5nRVthW&qB8 ztuCaDzO$lr^O*NKa`+oz>Y%SV-UM5z^4uAizRhZbIVHyYmVf!Or2Px|L`2ErqANjR zqs1Lu`zZu3Kda)oW2F)d@qjB&#=<32)~yx}T(YYypqcK^5cfSQ1nb9G#>QyP`O#$P zz-^5iz}%xM`Zl!t+>{*N`P-=v=IVc5>tgaT2e4x%3f$x|Aw{F}@y{AS8N4EhE)8CF zTLw$K={*-Ehe6kpY$|BSvF)2F3!sGKelO zxPC2-7dd$8)) z-O9Tq-^Fj$N_suI0+2Y_{@NpKa9Tc&i%W0zGhU4{`ydd|nL|J)b-MM7=@!S~L!%z# z0wGJ^@MAs-%Vwq;s>Ij*or{8VS|JktByM^oNhP|mtjWXk$w0x1xip=OW@Z2ZC+_^Z?0ItD^lovg9STe<2>}^gvH)H z%7$BzIMML<%x{){!cPZq(=Ojzg4af* zclgy}7d+e^fgCY?23D%C9DYQ0{YK1Eqr@TiUXW_JJtsxnJFj$YVe$$4zFVH-mds?WY;kF*MSx}brX7=(yrQNt`?VX8 z=2J+(1f-wBv{^kfFiOWBcH!1Ddd2WsvzU?LpQq&dEBOm&?@j$kCg*|lFQA(ghl9>x znegSF1sHgMR%I%bprXO%Ziehquj9iH4HE+hk zfv;;yT2Y7k{Ple(c0gC3=^M^^Cg~mX@NZdx?%@WNx_oZpT{y(g+YY8)p|6eIwd?h@ zKcYX`BVI^`D&8!j(;;}2D23sIihi^(#d-jknDM!-KV6?RA11jmq3AGoIr3wmy}R@( zW0_e5m@?kS*H$w&?rK1Fj2tzXg|pT!Di6D=d2$sV@9a~zno|+i`@&zK24iaAv2F| z%Gj#(A3g{&lOl^*!?gEgWW~I+Q~}{iLUe|J$)@ufZ4-NiK?^kzI)JeZcqsX^6iL%W z+g!S~{6oj3>vgFv2bny2iRUuV*E_ylFhYN>>C2k`z0cZ>pCAm%na%`x_62kE8_1Xo zPs`0z<5fGsjll#+PW*VAfnbknJt1LYtdoj^x2cR$8=&34%XM10f@Zq~RK_#Usbqqt zLn#zA+?uBC%`S@gY5=7Yo9&#c0nZURARNgKLr15$Q6hHAR7Z+~o2S@+-PN!+8)g3C z(PgB@VpcxdI&iLH0eG$(Re_Oo^^YIwc0p4$Zp|wpj$fRIu2x?KV(?oFgVM$mt{-2` zF5HqoJQh^=uVl@TsRK%&S~9mYAhM7Dz=@_MzlMFY)+v5dqW}M$TNF>vN$$lUJI*K0|1YOP0Li z_)KMZ^iQw>fZFaBT3#|Am6ed>wKk@WgZPX5?uC>THabd+(bwpg@SZaPX@!L;p#(~b z)B=}_X4*>l$U+G!0kD;3CpdE=3_1O15yM=(BuW#mFiWKDMRWS&&flI=ii;2-P+Q0q zX#1-ms9=c_#ax=EnAL!X5LS(;b|4cOajrQd4S?JZ|GCJcfs6j(#ot)IwhIm1pAyB} z_33w*M_1a^wgP}e!rkS9fvrch=~)m+a(tke6%$yI9~r;wcDTuj&yc%@k(kQA4c8F6 zN7$z3a-!sH=s?7VJv1|I;XaW>UKEqfLB`29Plw4cWjX#Yq*n5wQ~R!gZp+UH+L+75 zP1>?~0qV#yyYH0FH%S1*RE|BDUK$s-~V>2w+Dc5(!=Fp=j~TC1=5|5WF|cvtP;dIvNKCtt6IM!c+tM%VQ)B1cE&wr3%hT*T#j1P3i4Wwo7 zUl(J69BoMVmzwsMvCeIE$!<7X(IZL}VNOlv%grpiQVCsh7=mLoo#EpBb-kVzC3AFJ zT8>!G9wKGXJng2%(T}>>Jb*5ArX_gyDZKwuc>hti$kl#$Uq~4y#9T;+9%vK=*PS=z zfOK^>ksM|G!kgLWaZq39gi(!n;#9N+p91icWVN@8K7uh^?ccJ zoGh*6oKa@6wnklmj3MYvG?lU~sm5lF6#+F=;%z7YlI%82`sc=DB9z~P9@;)ffJP{S z`=~^bbmy6l&M<9a>`S=u8%~6+u_T26+^v^$#e|U?+PG2a62Z63dsIt8e01 zz!_{l_NWU*rATr3JI4RHiL|2jyOmBM=5TFFzm_QVkus1_-=;V=?5gqVwM28&^K>Dk za@Wpa24p(P9 zX^+U~S*53urP4oNcH6f5p1-H-6TRP1?@Fx_UV@V-nDXMz(p|KuFB)2|yeo?HAImX@ zFheaJTK0qVlZY7cOz^NO!N-JQMP$uH%j}mA$TR31Ot}0w?2GWZo)L%tZD6!5!B;Y9xs+2(qL#kaD01`5*qdJ z3v(HqE1U1VrM^yNh-IZySGIkc#2!Lz69GP=f}{xBT~P#ysujjD6=xDI^r_ziI9u%N zq6mhsFfy1S!7t?{sM~440e*4X7)Mj?c_QxSTlM+DXugHrRSGMdp#H<<(6rWfBlIfW z_fTAox zbH{w52`VQScDfxVyTk@_%w=Gu)6p={4<;9mJtIxV8I@JWjG#V@3{IZJX%Nm5^K~G7 zr6t$iO=G#k@ z%rq=%yj(FfZQ%gKr4zP*rGg}U`R!_jXp?FId@8kU(k+cCS>OVeD9h+))MK=A-CF7q z1mi~ePpVN%{pL9htd|jI!~7ju+Gq;I@|-W*yYuRN-oGA-jng7#Zt>c_l7IDvg@Y?v zREg|VQerDj)22oa=6rY*-9~V?v~ypdxKms;Hy@yCIuJmdBZGTI6c9~wOVw#;XTQ!P zn);d7?V-$SyaVFdP@~_H;Z=BnWZ4&RXm>)yH#&6$iLxFAfi*cJ6D)})cvi( z4*}5uv}jrlQvazzVR2wvU~#{Wwwu}~8&IEs_auRLvbs+D9%GyeTFdL=mpy+eN(5qe zzjT}+Q1eVP9ym5nAUc&*`sRCa19r-%`( zt>kg*4y(c|vwX6L2~a2XMT+wfkJAmS zUKS1Kl|WgqUMqHqxW|v<;dSu(T;y$!`i_xxYeF84Pzzu-wbi=FFvg5OcK3#wss-iq zB)4u{tODEG*>QLsm2k1NNs^>{sxT{*=B6`Kd0uSx@CupxHJ_Wl)>rUuJ~EL@o)hpQ2L22FvO1zCBXj(pid((rEHIp#^lRK$4r#0?Jk?Mo z4KLuGpjNxmM@SynUkY=l8C(BKjbKdwr@c`h1^v<#3z&tCMddwYY$wIBw1U2-M6$mN zyXbRx7awOuj!wcKD6MErn5nYQTWIa9k;#qe2M~!VTC6mO9ld(Qy~{VC;abpWcmuVn zVoC7Kbmy`iV_AUcvcK7s@7-IIxc5PLSl`V9gxp1UDOW&VUux3nfA?%VpUz=xyf*I` zvgnknT5{4{x$lN9%goEjV8sg&zg34`dixdc)n#utXWgOPX9cA2+9ARzbS3Jh}fB_g* z|I^V3>QCdE#O(6~28(R}N_-s_E1wzuRL<}VUR8@qeahB96eYw?rK^U9Xsm)h@pY_x z4?sA`Oj3Ygb`v9Ny}#zV0dJF6jqc+3baDVW3G(cIvfFF=52vVl7h_|$w`ISd<7tlr zI-adj&FHO*s;UV$E6g^MVoFTzmV8!Pp#9@zh{%eu3?(^Ek!(U&p^o*rf&F{3D=!Ez zCksrK*V3@B4uiIvQ=0nBmi_u4%~71+@fgHNY}C4vdI~`HS%2eKq$r_~igXY&d!obv}Yr$N{$ zjrOqn?UwpEarx#4$WK0`p@|gTSE=mMOGchvuNY7nBT<;_$RPmCJJJ7Q3M4+{!m139 z!kd_}+_S>Ic)hU&dgIs*lfeIAz{qFFYD`u8_59=&Yl`Q<)Ta=Tq+}s~{Au9r7Hku> z+Q$CidB(EaI9C_SOI%S88_T5omm%lF!SWQ!g94PPR{L`vmP3WF6J#6ki@Zq6e^IT? zoOTULC=o2g?DK9(hxX&jl(7^~*P=9$GRZo0?+wo3uUCa>KR4_4b;93TD$r@%d_~k! zxsXd&3}wZTUYqi5knmK;3(If8^l`>N*FI=`D-lIdXJTu(>$as%Qg!Ejv}$NvP;%!0N#xMqR*zF_R5F14Iv$Ds>3(Nl`x}1pw8TP+t!HFM zIT_d6AhOJyrq&*yjFpyT-&=Hi7_>U%fbq$%kceyAkzMy&*tI@BiMs~Slotr^m$eUa ztJh9meJh>NMUzhms?>n`+$hnCH$$S0bGVd5jCE|u?+j1-Sh4{=CsT1iJyZKvVQoyH zM@$7kDhQ*;W(o*LjnEz%nzL4yD;JzqlOwI(L44GZ6+Kzv!weD8;c>8U2|dq>_$v5z z22%#JsI?zm^&;CL_OH-8m`uOFs_S+_Q*7HhAj{8cHiTLuI$|KE*I_Su<6ukVmw!#z z=hl+QGrMy5SQkAKVP5uP^-~>0e6g?+DO0C)Cwm%wlLveGd(aXr@ z(%y`PTH88rQ|u1W^hUtJLC~^a2=hCE4apBj15@D!2Y|;>B}y91$$xgl@71i6#Myt{ z;nBCH!xq(4?Nw#2^B0|C$J~#NdIRYk%@H=JVG4o#4ve_)$gmQ$_Wl~5XrzXpFc!Tg zi*vdKWlQ)aQMf7wjHDKuw~Oq1d{x;YOg+wlpq>v`oSN!!16QLulWuRCe{+XTH&5x3}s)6$4d+6>32%pZ&Zf%qjoJ zI63C6nqQe3$fRgVFq^V-v@@c`ZQKIhpz!GS_TMgmo z@@c%Ja?SZVh#M6KZ>PM2tu9O6jang`HBic-ORwh+R2!oX< z76yFCHJL(~IEHmd5hbATOm)m~hif~Bd4MS0zAUTwy#DCa?k_Yx>$%!j2MU-9LDNFw zt)Wcc7-UXeo&0L=gKEjm5h%4s4ug)WrN>FGLrXr6spC7~z~v8_cs0-9*7RC>y7%4y zc3K)^S#tr)$FuHuxBY99Em7D#m5B$gk)C(?iv9*7tJi$Geeb@v!)oBm*a8&jTL0qp z9DP)6`oHWIlQ6#OKFeaZ;XV4w{1ooSGCT~271vJ-w?!hf;0*`L?RZr-9h*3e_((4V1_!I-)_7YKC(vPCsxpny>K0?0&-w`(B>*kypmh9R;?MZZr9VZ67~j=bh4QMIDAv;Tv8O$^)7n2jMgg#CX$Oi}fBwbZ?``9` znd)~QK`MJt(yT{yxuoRPXZJIz5HI`Yhuv8mrM_%Y{TgYga@g{<@>(E6pGXAebbb2{fQtIre zVwvNpRLD*Z;DA1g+V-tQpA1w3g!ZkYiHoI$fibIR`hS!&Fu=bOKU|xpbyO^>0g4SI2_6Y*@v{t9 zzw%pbb~R&J`VW8(gn2a3Grgavg9;QWQFNCoFG7L{MG=rYnW^4RqR8h(WElt)Tx_t> zS74A=G)@_5*yn@`LWaL7a3-b%gj9)P`&5k99AJ3E`4a$*qL)BeUOO^u-csof`W48s z{Tb>>X!{vG22hQNZK#$6 z8HqHVl^+(nPEc=%uhsa4G9>jZOys4UF3NJ}DI`VFxpkk*`aj0L1Dxvq{r_WxWTqlS zc%ni`h-{){R5BX&Q?hq9M})E}71>fmG9r7N(2>Nk%HHGHj=lNcr~3Y$|L^&CzSsYK zU0reNyg%=8&)0o_?)!PLFysmqX^*MzXuUi);lTl^K`(_?q;S(OzmkDQh$m=-D`a&W zW4%g)9sPTfcVApBSzzj58x6z|=mdhKV}tatJgZTUO%JE{x4O5kXP4G8cOq?4~VlK?ef;&51*n$kbw+}2wPkn zR@I2st?c$kQnx1|&#p@D;spjN30nbAqoAi%q0n4xWGl#$VPg;i^z_?6^#UamMHGp4 zatAC4JK=-HcEg|1t}$pt`TX-{(EhSz@i$9v0;=~ZQX(jy6aImYjjq@}TR~1J{-JFB z`kC&th(H4i0aA~w2+x}noFwDhuHu9{xP18Syqu&t8KK_|j3i&WNm04Gt=%O-oCwmD zll|4mldXY;GHHt3`b|F5uAJv;01DVoCF zXQVT+cOTOFY~w=3KCWphjt7+U+oI^|?U&WqV4b;mtv9NoLw_g7`5-v8zFFh|z(o#mRxynZE>> zu)GL{-!uJsq?zYu$S;0qp1=@J%>rds^+HABP0Fvm*}Po=#kuh2vT5$om6dyD=h_y@ z(dBP;zE^%`S?%@TiU#JoV580X8skkC7hevwc#=Z2s5>=Hx=%Qtqde`#Fm7Ziyw(i` zaN`=7DWO(_sU?A zlK6~E2jnoBtGEoxwNJQo!v{Y4X8Wjw~q}3kx4ogmO6{ z85spS9@3ex4;9Od`Af1}z}JKkdV#K*iROzGyPnR+^aUu(ctbV{uV%8Rj#QXk(rqg5 zM4s+Ggy$oTY=4-@WG{)&J>TPBus{h~>ir`EbJ zyz-g_bx472I+y}?+z55z_tdAM){`p_L9)S{f^_Y(&FASR#wM%-82Sn{;_o-Peg~^M za99QXjMfdoYiX72?m4~bg1Yjhfv2&7SH?ae!T^^JXf5Kc?rHO zVW1oyi6or$tYLl45y#3;1{EdAg97beCN4uY=kNwyHv^sTcSwezdZ-d!GE-sJ?#aRd z!>)ZK41GZi_j*F40oP7qk$gMz9Wnn~XwS3NW#4<3ST?e`0_0kw#=XN##>v}2Sednm zFKc$Ur6`IljjqM6N3$a!PHazku$AJ{_al zL>Y!r5x}?|G?V^@y-?XnPj@-r+1C6V#_jKzpKAvqY^jL1($>KCUDJ7^#SIP5#%|pg zl}BhUht7Afjxx{Q1}m97QHi7_u~L&$w_FgFUi@v9hcdlCqSN;*h$_ab`Zwt;*i6yub?hX4H21vfcZmL)IRp{p>-V8k#EC{K(%h@c@Pe? z1hqgrrpaIJPM8exvns%~`~MJMzSrGm&J|F2=K~cRSAcJ!hId>$I2{(;lP&He7D?48A4!zNdFAC$O?wmy*PokDDuDVxRWd71cNGsfu+BQY` z9%u-M_Xc|T;vT>ti-XG$R#v?uXsx63@h18#f+{Vh<0`{`!fhthCEqIkxruL$+PewB z;i2WtfjZ;tDe=&8}h zZ$$4%W?D^VwvqxLU0>^0qUsjGkbS}lv)^y>V^ovj@re+b9r<`r7ZOo(bU}sCEM3Bb z0&&nmpPfAx<_?858^=Pb*&kJac+FrR!k-o*f32dy>2!8OpY!;ctDqGr(rhLh3esD7 zC+Vw#9~B%_T+=dKgmqDjt1xVomO1P}eP``S2zlbv5h?2xDzcGBANqv%!jj3mjFX;5 zd?$OAKC{rDqg0X8D(6MC#<4xOhMPnMe(j54K{Cawf@5auVD5qI0$cp0l#0$&d$e1(fFtb$LR$jh$~+y3A_7(T<)2X6m?* zl_k$4^PBDwkG(-XN<-hSN_d?T-SlRvlQM5@v`@wDF!F3rD&AeI{SNN5iIjT8>h3s%& zv`Ioof=YqFsQ5a z`eDHV%?l@y>L?FR9i3(KSv#rQlvy)K&bt4{xQB$T|HAEi!WmBnyI?vy7EGa)0};Ew84lk?SDYTvH9f zTxRjlPdEUI_~>WWY)4sN5Io$yxN^^f}DkaRq`kO^-C^1Z!1g9 zGh7x3z{}3LQx0rijSP!&Z&3SB_z17GUix|UWJ>$O?c*|ryT}29S*ivjs zVv4dQ<%=De^0{UQP28+$C{zS7ZbQY76WEWR>ECZw7A(S1!_w-#V+T6m(H=Z~@4%w= z=x<>+J0UEw79?vkx(b!cdM?VI+qm(u>IxWPsOMkIk7jB}ne9cq)wpUaF)W3PhgbG} z53;D25nOSi|KaK}gIGPA)ozjbY7%-S%Nk4Aie;<^UjV)qdADOLZgkgn$sYVZ1er~? znLI$TsM4Tv>_O6QDHzM?%&-m2--iXBS9)$VC~z4MT}8Foov_B9hWmy3-?T}EduC=HBZ@}oP zFFQeDOr2#%enC=LQh-uh2%0;pQyzN88jVm&OjN7r=qNT7=fUESXJ6mmujrPTIE3Cw*)fH|If1>O5>%01bM=7~mOb%ZOiOs7l8# zUcA6HP6v#DzVJpx#X?)X>kFIWn#=>A7%Ss|Q;X(31xUI{Muw#)YSWE{*AET7++!>} zOpQe8pC)vhhEj@P@+Ah_y^1ceNC~S<>_r*G>x!6xiBS^ot&Uf1i z*ElJXTqIOemUF)O8%6mol7V^Iu^p2M_Ob9Lr-5848U4gjr#_Dy(sz$%uNzLXT{%QZ z2!%y+-bHmA(#?&{t6ATxFhCP_P%?-k4N4u3C`gY;H3Yz$B`^Pp1jY^IV*kbRZ^>8T zji-3I`W9aF%+_IZtw7|LZttjH zn$iuT%K;iB46S2x%-wp%KL#}D^2q^#na|u`b({LuNn>`fA>(@K2i?Ib*b_>I1df@> z`mZ~MItZ=OJJB)er!N&h#k!{%9;%$YO(K1H(tSgqKTm&72ok0S>Of5#LItsmF9RnO z*}vC(_;G0zR)3j#5ZAhtYGWA$ir<*DCGaKt$z><1!BvK;Vs}c4kuoNZk9JH0clD!Q z(WhVe@=}eA$V`#bY7z_{3hdQhBw&vQ4>eKcGOExhy2(8W)L0+E@Mm8#j2*JS49A+}93?5e)Xrk|oJ=|X6S+rZf{~+8 zi1~?qr+rV6cwtYkNiR#6Urs*_zN`T1y$utZNacIjA_46ZZ%+Jb%%8ScKm|9gh}vM*u%nCt$P0D~A$-*%;scRWG1S~pUA$#m}t1+f&J!|t=wu{-kpawt1A|Egbe$)H=S( zj~12UBD_r6#73Efx_EK$)4Kd|>5qCbPWWdPOo3S@5;?XLhEs+4S++Cakr9S|;a2>c zqn7`OBw7MLHxn9G4}AG6h}Qiyg8vNuulJ-Gi&>^=XsHM$rEU1KJ8J)D7OksjhqQ># z#Sib-mC$f)B{(XZg$Dgt2~t9&@P9knw=*d9tPmKSWSHeS*v3Oc7~Nl&kYO{aG>CJ4 z&EQO_4p#gt+slpV0Yh%c@uf%?d&!q8<6w=@dD+5S?DJC zK~Y|h+cpm359~~{-;()4Tk`9g5Vd?eU44J&Qv(Db;)`@tfW07|7PYsT=dS=`CKe9JXLalRm_Xi6+G{e%D&=()Nq@qx8_p$#@iG*0+#8HnMlK;H{W2 z;aX4T4n7m&1w+Je6g5Sk(I_bHNVSnV1W$G6>ATWpEkJWb|A}uv!nqpZi!u65%&{SU z7r%arOXwZ1ewUHqmHp)g5aNPD%k0K?)jU$^DtGg3%l=Ql(&M3{_d-RkD=%b!gP6^# zf^P6PWpCm=CS4fF_n~XzsK~bzcWDsJh*V;$X8*a0P!U~HtY7-XQqBddgvvmMouz3q z{}svQhzq`{i-|M)Mb(S>AaPFnEH#%jr~d6$jY&fwts>LGaV*2hd9s;x7wO8#5#y3Lw(a^Wd;JlV?1&R{;|DM_(Dnv6`6kzO^_4t! znwUllP7KTP3<<9g#%;e@4|5=$zf=5X!|To1-Zr_a1L5%Ait(GSRLzjGY)bv_;F=^W zw6yX&`2C&s({O5iA|7lLZRaxppon-2sPMuK6_yRv@1!55$`n@H51i#&ip$E#xTi#Q z^N}LG>_!g>uVyRN#w$t6&#(!4)6i~-A35yD_f!cc<189B6>t9{8(Hg8I6z32T^-l^3DW=Wn^U5lU^>Vw+o1$8_QW_!sa9Wt~@l`x9RjMu8AN2 zTH8bBBDxyu;46AnPNN(_PlClE+Vo%+y!KZ$#-Fyh#Sz=WkT_iH*jU7>|hzW1CKS&dn*8ei*LeSE(W`%SWcaDdl4H*2r@jS@a^ zqmDf7it!M1bA+tHu6qMPEiUajEtlcvhAF+$702q=ak#*#7>D2dkn(Ib{LR4q!aLCk z!+-Rb6;DP|=I6bZL=LK`C z4aqN4DfJwgKQ1QwCZ3b|E33_y6-+|@X7S}a6~Q#`AMOByIOwC#@|r-C%X|%QgRafj zHj^`F3P=M%5r=}7nFxuH7>n|hvW@kq73D_|duI1M?EHefrsa)DrPnL$*X}nLH?rAj zn`LPGUFoS#Fl}ZETSRs?1eUgsdUv$G4N0D0Kfe7cFZ|=w#@ykK z1L1knOEX0o88)x?=NqW$~_&u;YEbmf;RQ)uSf6t#F8?Y`s9HO_7< zV;X9{zPKhV!|c3~U2*yt{_nZ@gyPVSW&1!6yVA#F5Nf z-EH0Vc*&x)u&usM9RP*~DojK9){|xx`a%n7o_;t?A2?CQbZ9h(Z1n}%9*Jw=9C21L zlz1qVvGG9&iBF!&G<#zn)u72zBJa$SP(RA5yD_s4Z~$RM$1KTM02H8w<%`t9zSuso z1l9YagUeRCw)>6DvuCdFG0q6zljQ60*cUNAcq;1Cn1oNGv@hv1eo7HeAyEa#{_@*L zH(sRst1@~t_QM55{1A8P`g_v@^}Gk%k3$q2dX~t>MFLQUU?%i)7$Wc>1FGTb-Ub8z zD~-}Bs@BWrH{Oxvx_UY5wtic>|7vD1@1dnp{Wl~P(n0aMIxaJwiXQkfIOiaLmXNJ2 zPf-T%w7eHOU9!$c1F-SvMbYJ-TS1@`$Q-99r=pwa@{Oiay@?+E&?`pgf#^D%Y)3;< zNHGXv^`y}hJVTs(o~f>d5yYNc^JygLiwpI>NZy$6T6`e7i3~rfl+co+Oc%PuN8{aI zaIFg!41XM;6q==deZXKtOLV(3y4f{sPvQwPLlC07jWWhAkJPR^RTe5j z$0&o8;BW2>WtNJae<61>gT|_vR=Th*;`vuUDA6$ya>pInb$Vq%E+g3#=b!u4Z#*rY zmvrK=c_GuI3Nx8~9D|?OHX#Pz z3vb@VO6pa8BXP@Q8NR-{aaBjBa5ux}pn4rnbHM#!=c@y6ABn^;4=4a~%3*AVnL2P! z2KptQ(r-#BNDnDpg9_ytC=)nF9spa@g-+<_!tE)(m4y%#zu$PyXXPX z#_W-Boag;MQT4IGX?>+xD}MRGAPqAOk2JFY6Wzw~sE367*FfpDv?(^OD-|I3p*2^1 zkh*KYt7^ThC@cN?Wr3h;zYpy{Pan9H{U~1Z=!T|WX9!-96nnim@!5f*Y=FaP+nSMm ztD0yRkw)sWz5S=aXhP6bKRr*}ov(#Y`fqxUfy&g6Vi`V9#kCzL5Y)6c;saO2iOE3a z_|eC2_sxM9fgHaS>fnBS&Tc(vq9Ku1yEZt!U%ysLwDUjD|NS>!Yh8V(^RzEmYcrY= zkJ|i~AAGnHjE(PmONw>h4=%4~9hYq_nf2r{AbRZJEAKqDwKqJd8}ge)s*~?@NGIz7 z69L+%eIB;1*1R|3hV7T`X=}{Qz*nBScW46Qf$Y%1&U7(5f}G;LjZ-)3{nM}w=g9J< z<2;_U=T3|L>ugYzU$^;Mv2_dmmvz2t@*VJ2JuW3vG`*toUF&`YScJ1vcTo+cSIHL2 zNim;x;|}WYBUExmr+`PrYE*gUVnT<5?FT@WF280O*6AZ(UZr@;mONgs;ePYZxvzK0VAPCxSR>Spv?upRuFop|LRTFR*k5;V+T4k3>#N+w;LW1To0c{P&u8(0N{!ErsJ^-`a!nxZRhENrNju%4%z^at={Y1 zYMafji*q>YFORO*{&q_Lvo;jT=EOX z^;M87*{3GF3{d@SDU z-5%{l-i_I_`HP!sUkF$Ge-HIbY3*pe@)H>uH&$*M1iljT%%N5l>OVbl%lDcJw9m6= z1_%vD_U>AV{oFxex{8@s=-2^+pG2hujAxacD0cR4?8aSjqqV1S{L!H-!wZ{bk+Iq9 zDrY@bSDPy4#_rYAdJU?ay|xOkO~N^{#EXU5?RhOkpL_}yXx>m?qUT;RrC0C&wqEHi zY2&{M{#>0xXaDram!3I+apfJ`@f%mI-!4R!+*)~B)XAsgdkv!ws>}qeQJQTenoOo{c-KfWRJ>MBT;i&)b(Uhi{ z?SIpgqi6hl?sRR*OV29-TZuL=^Rt~j)di-yX0{z(_)nNT&0tA+NoflZxn_l%ElsB4 z97HS?NQMW_fjKUbmq`7t89HTm6j%E~baBA$fo#9SE50U~PFL$4@z=VQKS{dge5YoAU@=`I(Cdbzp47|I~1zxTfe>O^sK zh*(dH-B7^}lMpq3^Lp40;~<+iZ;9W$9t1^@9w?1A0Fev3v3=GPVd=K}jb#|tWmbGI zvCTmL6lxZrZQ-L3*a%`Uj1~PcUI-SBHG%hjq-11p+I;K&Msl_Aghh3qP3&@&g!pvt zmN{rN`1iG;7t0@X9Nqq-=l`>+BnTbPj2LFVb3SBN_TKzt6I8rIzo%gByB8mWfv&(B z-zmY6a^@+z(s9=p%njC8FqDX+{lk#{qV<)c zr%il$2dTzJdPDQeNY*-N zzgIqE8~+K}+MhwcDuV_xe0xIuH&5hV+|~T)gai(wZ(X4>+4|QP|M@kO0Cm~HAqO&q zD^Y;9SrcKgiOGkv+udylbouAOLC9EgIiTn!oAc;a5W@;Y za_F6OjeCESKTU$_iH3^9|005arZPSVM5$Qy2-tZ$9zO!r>aUn~EHZ;cLS@qLp&704 zfu4PQ)vTw1%c9)4HbC9o0#R^WvAgXYS3rYT*iA!ROaEMtgI8L7rDl88yOwMK)a0JF zBRlUd9~kozH|klIJ@U`<{`FJE1m7D!&METB*|FV)1y|~G-6d9gyHjU(W?bm}Xyb)D z^4O_7JvJ)`Ek@d1&!7vdJO^D|m)!ze#PxUA}+Cg zaLQ`nVg-fad*5ILeGMj)L7Q8onrVUkPi#c!d}cZLEh2kb6y zb0?xZwU3_5OMmlSQ6-_>P+b#61H_m$eGkbU)J*^z+O!bmfKGAFa^K{k8Q+SoZqg#; z!FgN7N{|H~ll=n_L!vRoxJ$g9J)?dtiR=mGCM*Xk*;2O@57ebd>^T7QPyPi&pr zljsv|yypJlprRXu5$w9#2)0r08a9H0#m6U}Z02wpfJo1CydOnXNx*DrRmKon0K=^Ruh!&c&SHY{Q{PPgNB z58sua-GO^KZpwe3<-~-^HTi){v ztNZMtRtY_G+{hu@ef)ZbkZ0B`O-=a2e;I}UJYyhv=Zzzc25>uwx||XEDy-@paG1n0 zxB4f7dxO7CE%urU{;g>-spK_ElRwG{IpO zkWG)cu_iFG9Ke1gGWX9=w(}zpeRsOsAOOA_$Y2Q&C53Bt744vf8BJPf8p02QMe*k`z(t;1Nsaj? zejhb~{p($_S#EL#XxV62(sQwcGJluOOpdH2dSMIR{6!Pm^sC_7ALc|jMvT6ySilj} za^0fxi_hE!dKG~SB+Qy_Gbz(vK2)}sE3QCeMijCD9qrHz<-%5fn&{_e((u;oBQPr< zCa6tiQvFT61!&jdG|Z=y{Ara9#16Q5DkqIrcVF?#A~7ZM~z;{XLllHY5|Qlj$I79urz#`2}$CYpui347{H ziG)D5B2^kNC?P#Y*kN`L%gH$dA`?Q~8EgtGka{LmgZ*3qw4x#wK)SDL2COz*b&VSe z0WaYJbW(w8FmkHzs0@~C93DuHsCT5+nJ1OG616poBuK_H0alLvX+8GE_|WscrS^6rVEoqkxY7Gbql5@=a6Y($BQI<)s*`83xPThamctcq^$ zHzHl3Pc$9OT5%O-T_nYzQE#RUyW6rnhZndgyU38%4<;3dj}zP&BBKW)6dnXKolcJV z_%|0S6T1bS0e)bFlL7YEZ|E$TGfR(@=e^AK6uM^TY`71KV-O!0HAJeM(WjA?!2OC> ziq1cr1Gl{ACf`<%i(MH+(9p^RS1Rjpj!h)dd46t@IUc`Adl1qDpeJ|Saq@oq8^$I9 zpB;p%uzbfJBqfeYLMrioo0q>|lla4mW-lR9TAFK;5^I6qXCIp zS7WBMxriSL_}uzb^021rf6I|2&^wC9kW?AsH;wLh*-SL9=6xE2V2p6dakoAbkdmkAc@LJ;_89#7> z@_ME4xt`W;IGBTg3=thHq$K0gYIJ_yS&w>JRp=Un<`_|$4q&v-`4!#?q?95Y_&j3# zSj|h;uXH2xD$$90q7Y{eumcUaa-^#k*^}t|9j_SQCQs7Rr^}Lk>2bvESr|iA?tW4< z9&lIw)_+4j|IDin2h8zT5Ski4lG`_S)O#%m^nQ|JJK~tB3FR8@^@9aK7 zeE<30zkVVJW%^^}FLWdu{Wk1l>svCo^L=9$$|_+k`iI+PA(IX}7Um}<`k%nmzrGK@ z61Fs}K_$Td;{Fo-f#-I|MPAzg2RBukiJ3nTFH8G>zwtkxpq>KragpEqUSPV*Ko-T07+69v0qr~qud=H>#PZoQr~Q7%@_%Od=Ldo`!x;vk7Xrc$MBSv4 zH-8UwLdV4(y(i&IZWSpy>DJ3r6kk1mzv=h$dpo`>vwZ2{JT^;ss|Vb(3>CD`H0_R_ z$2w9NqG_8Qn2(l`4wsS~d5?D6qrxwI!>>8>PM9TGV2^w?KHQn7_Za01c}28erZ$#I z;mbYO4)?xq+P=~M+dW6w@uC{T9-=>(b{|WVQ5s_+9hWZf5AMj>m-!{{X={r;iSg-h zn}gbI%Ss`0KmOUxzkWKSiNxFG9`L*UnNCeJHt?IOfm~dx3iiu?askB8hK4lM}i`hs^Qe#msq$@+#VfD#?k*jC|#dMXsq>PNm%!uOS0}GDi8t zpwS*mw10>F55$%Z3Z&##4Cs78&wP79Y;E@{T5HXIXFp>So@C*!~7 z*cBFRBRb}{rSEuyMI2GDI@euOLj^e=M20;->A06porNm-3rgrlPf5{uOc2u*@Tfg; zihmqNUUYYA4G%*fF69uJKbB3O>|LH+f%N`WS>$daOyT%r zxw`lW(aj8qlQ*ria%pnZ*FSAdoe;%zHQr!TuCsXOciSGTrg@HZRhpYH)vb2=G)4!L z#AvEd#4S@>4J6(wjIqOpX^3%#O*W;Kf|3hWk?_Ka6NzXnr$@T@PBvUS`;O?AdSVbM zqYvg%(jT^AF&atDce`KtiP=Y}X}V|?Hat`1g*TyRn4aG`8TUKC;SVzr*1!*Ih)S*I z=?)Gyh?Q546F(aePw@l2eItgCw&S?uOea)a>v}R{LfTm3b9df=D9xZAGRWhks$%m^ z`!yLD`Dp1(n$fI9e88M9Ew>GCc4F| zB=SmXnjufMy@l2Vxf43sSY4Wz{r^8E`W^z*)BEycB!%48Ju<5SkVrR&o}c;V1O;7^ zDneewy)SJ9`h|l zFOx!o&wluC3O0@R;+DjM4p{|T;3x8Fan?^`Cct&(Buf)rFE=4V;-O1#!;ECA)TQ@X z(Q^+KGm4r7frjXoC%U-VdZx&~|01mic|kaR2mi#K>u{I%ZfQkRn;%4U;lz$h9ywn# zH-yXdBa5XXHWAtz&WguGBh|E9v!QSE2gPMaN80#kpb$41rXf}`WHoanKQuTJR8m2v zp9Czb>jJU4kl9E?igT~k>BFY2dF`fcHXshx#ItzR>+(dp=#ic)bryvq*ZNNwp05dX z|G@h~d_S?7P@B9c%`jg<<;v)2K{jzKK3X3)b4QQq!`eJBk?UpO)hh&YfR6JouYXYK zbKbVMR3a)GitI?o1Cn@|M!q%10y%tg2}pN{CqdmjE@|_V)>VNLxUBA4K$U1BL;pcq zs+zI@jo(9uJ2()ao5t8^)@C1MP32fxIiax?ZJwD%`NYOx+Za(JIEPwG{h)#d?|89B zY_`g%GDCWN_njxcs(KF6FJhlTMHmA^SBNyo3T zrJ~6nYGAuB`X?Gb^OYiRdinZkL6s&b6op%}=XbO zsM#3=8@Fk-iE-cy0&BPt-55uewswLHmqzO3%lO#xRtl^FtySWg9J&g10Vc#)5d?WK zv`4nt#Dtc^1JOiD5kOV2y@YjYX(R_PNk2Q2%^ylui+v}*eYrZFkQVai zfq$G3vodXi^+wdYQfFd69vZeA>Y8!tg&rmPb?LI&oNL~DwnjDJo-jl=gI4l19YWNv zwA1s^p7Kl?`6~x}xc;}Ys!?%tJVX6MIDFJ9W3C-hHH1i0kT@f$)8778{g%e?UE#yW z(pcSO!-Avk(e5}hGM2)nu>F4Z=MUS67)(zm6E@*voG;5AZAUg7$fh7zp87oQ9`{hv zCv%Ux3So(fUBti75d!K~`-drw?K?j)=$4TO*C^t(Q+4Xob01*3j=O(&=C6<3)V0(( zQG2aKs0!~A7T)ywY3pfj%^K|B4LdL~&Jaf&IJ&jZA?SekGet`?6aZEC+;)oAa@>^K}zvxqX=ADqF5AZ}lJQ zw{o+e<1y`rmz0T&yAKF^cWoKiAdn$sC1})H8%w*x3Bj zbN}xud4E`1(nT$seFzgDn8FC{amnwv0dUp|IQ67Lg3Gfw!?QOyt;oy4OdMbK?RVQ6 zT|jDnm-hO@Tz6mm++x?j#-66`V**rYHBkMGxkXF;9mS)**?oDMr?$5r%Ab0FQqm@T za4|}JLX8iJE*vi6Gm9?5Botzr)=jrIar9@0U568EqjscMt-qOp(cWHE zsK5FVx(4nPs$^Z#yvaRorSV@3xG|ZJ=h4^pN;}qVQig~7=!B)*+qUiHpU>v`EMLX69%-^{VVZF z6?5V3$tgejT#E9*PMAK-^M@n-_0fAKxalKt;w*bIa}`OJ#N+q6klKShbqbC1rHQxG za+Al_CNgvGD%4GwdYwvFSQ_r8cxfW71v0Y83$qrx;oUvAF^JDH1C{Gc)4&;$lpSVN z;?t41NgOGR22l~t$FXWw(Ksy=6?cZk_!gMbS@%w&8kP4Rs+L**%dJAxKL?Ll%e>}m z_&jb>&QrorkG#3~$-HNt+>Uj0*K?nT!MA~iSC@s@cMWcWq`BtnURgitw{6d9;ze|` zZpTT-(bwX;Bu{}|%R;bg-}T;nydO0I)aki6yOuZmX)(5g!`G5w+cY$pGfcr5+qI=p zm+oUn2j)%PtqB9=>Gunzt;^Mhb?FtWTV)x$6#UxRxgguv*+hswR+}588PaTAw3o!} z6q7veX4~RIRZaBKW_5zm5`U@LX>e@HCUg}eHy3d%4GI_P`;VKBFX}qp2sJnHoXso3 zTvwiU-L_w1*v-x2q{C2cG_RGv@t&()E4KP~p36ULh~ze|>yj6@ z6uvs1b#WyieW8a!;<97cqhOil#dGc)jB@RqB1`VTIx01%xqad`1+p9fEm%&Q3Zg8R zgC|P|PDBJu#I9ayr{8qqBx+Y7t;@YZ^qkiH-=4>@lctUb!D?sNIiJUcgZ<=VMD~C- z)Bh59>WuTHFNMVn!!^XTluj!iup&tjnG`{ma!8 zW+wy+99`7}{S7*tStuNjT#-dGW_2#(kkNj_SNCab$Qk#ZS6aPA|W z?{p719ZTyK@z|+y<4}%Y1g|a3tUYJ$!vm@_t0^Pb5)GngWwi^3tV3@|EPBE7Ka4n^ z@!?Dq%lT`E?MpUpqzBT?h>U0{66aUVcZI#KTxyn#X*Tt80%e4M$jT!i8UIlEv`E!9 z6k>xz-eHk-74&6iW1Ro6^DM6Ke2qyveUKA-jTB3zHsx@_LUD-5if_}9HSF|+q)piT z1}B?-%PoV@!NrtfdZNdVTAr^lB6~~Mx zGrP(){PT-lWjGcKBIg&apYQ2Y@<|ePtZkSwM?a$(zJlFh#r69Y#A&lb1$Hqo(Q#(r> zaXn=_`S2naZv(kxp4S~3>x7TuFQ2yRlGP75fu|EDT0h+;?^Ktra@&_#F7X-bEuVvo zyC)=%yofvG#ChS;q|dXsaLnBEwRPMn?jfMnd#UUnXEz3qa!X^_=lS6zAh} z{RPxAAF5GRF-KhmuoV7wi+O7SzzGvs%v$Ce9y|mm$*$?2u}^??f%zOK^xM!+Kw4>e4bgw5{@E$iF8&Md`PE=hHFrs z%Qa^YgOhu(@uX3U=`(`qXD`;ylv;KDw$1an7sJ~(gI#4UskYmnwob5^jsaXCeo0fT zYb$7N{kdh^hKsjm;-#&v53GuF8_5(+yNrx>7R_k{{j7HbE~pvguPswVl1Iyj81yw`Vhpv%q@))EyclC=e?e zd(P92$JEGp4d~CZV*_2f{V}dkD->I^+%sLuyX!7XhhxDIjVz(wNsAmj#rQncg?R|q zgD#~A`pd8fF4g*u&--oN%Yr! zh<0QWyIa)T@wyHX7Gt;$OKA<0MzSh|(S+u}IK$p^qxz7HK9Q zJZyTq%a}cEg*n?l7e+e?PVqU=cKpxcGTqZ$2~fitLJ+tbEyHa6$z+lbz>+`@E;a&nsHHIfur#%V`mC=Xm0_951evo>R6%Z0B?! z^{yHBnQ=<4bG8{H&O4}2<*^%S3`Vh-=y6V`M6w3SsSOj8o6)bHw%RJQi@OQkDFMZ2 z2w=TaPxZWvx9`?`c}bTLV=nt}&lyvi6g|$}TcL7MHb%K!ta-ajz3DsK>uZZRBhO0W z3h+AsXi(BI1rV~+XI^8lhfMGFT&tT{i+S8QZ_J@g!Vc~4-9lD_P zJc8~hnbBEOX(@u9?||lu z8pyR#O+Tn>VlXkS&pIL&SyY@Y1su_8uWX1I$IM^Izbq&lc{@w+u~epBfR3RrWW0 z5eLFPR?tqf&LVB(S~U*5eHF4xmKxr4n-x<*GONl&cY>-thH7l5y1c0W6TaQ(`Q+eV z-3=&?S8q$G+g}N#cXjJ5?8*wM#aln%mg5Ro6oe!N)ihNZ=RK9Bt&A_eFAfgo1fOpL z!bLdZxErdIQ63jk;`7$8Kva8(k&3}dPmp4XAhnD{R3!ReGSN>1MkQ!iIAFH5r8n=y z`%0xr3j!j70)?24OPb5QV}EfQp2>NAXnQ|n2q-R`EOBHF^@0$%+)S+@WAYnqydeXK z?s>2ar(WEtAuGX`oqu=gGuA{c;e5@!nQGG|%~G?qEgfa+vkHms%#6F^8zz-gL5r9l z#8U=@JqeLjkSJ!+G#roc!mDpSTSXxouR{n4O2|36Y@}AO><5 zUCkNiv%%B^+7u(sFDUW7Nlx1L+oYehbG!AVL{bD5w}LJI(%Wf>4+k@(Oz^bj}k_CY<#q@wt&ikS!|k{P5ty)X$GVfA`-AosheLiY$e2 zAH`BX%Q5}}Fc6BlTKB_4zWFOR)y`>nuWU7wlw95Rt^ypXo}!)j$)FEHnD)>nOf7ic zxY!&*k;$&| zwtl0dkOdHy`=Ol|^9Tf_-=Je6?8eqUi4akiAb2jy0u3PtJ0GsUVLzPdme&JR@=w3-FLcukehkiW&MuMcf2C_EZ{#q&^k5#0Kk!VlNYA zKLqk2r3mKlVsMA3*z8Lxfp^Yb1I`2?Y&YCj>s=BY{N`cHqo=Je-~kH)+~Iaiz<@K) z;wJM(v1aP=m7YMe(;FssTfKkW(60wGLC{QsP3gOxM!2UVFKPXwk1XxX@`=%K+}uOU zJvekZW8j;^W?N;SJ&$F!EZRw~{zM{=bNg_TO>Xs`SKb2eq;c&Vac2z83Y1l_g86c| z=Q-@*hiQNV0nZKt15>5O(`h)IAYJ|r&WCuIq)L*6HI?X6*@*Mnn+StSo?~gFYtrjg zT*WO@!TQeI_|MmgYI#IhhIG8=D@$gdn9S@rX!3B{CkM~j*rN%ex4SbWcwgS|3ve9! z@pt{!B)29;S_BYL{d~=>wQ8Fah z*JV2gz#vJ#vl2}Te3R0>b^83glZv?2GEu7ope4ndHpx0+l?8W?7BhY#(p?bj5 zM{c3mgPx$8fa7m?B@mY=#t+CVikQJzJzrzhI>O)Qx(!@t1{^<0I?e=^z{4Cdqbp?V zo@TKxjh9ZRY0&`C5gjDC`u3~gm~d@o@;HH%dP8P=m;8;i>hP$ zX!^NzV)#o+<*6M*+Frdo`aD5S-q`l})oW3WOMJq;dn{fwG7Ik|uXFZ^IW3>lc7Een z>1$Krslk}kL;*GK_1SM*K4{d6{%LHS=v?o))~yKcA=M+JG8+u#-VG*7x(CI5vhTC5 zscUq{uaG2feDP$bQCPq1KMc0(el+D417k?)Ot{$Z5)*%r9mahL21Z8Ma!To>r>*yt zUZDnl}bqjKU_hDxE3 zqi$p_$~>GTPML0lD02gu35jDkArd*tJP(nXW1jh~w|npNz0dQx@B4fI@pPQ$p8ei? zt+m&Bt=HarZA=d`y$gwARbc)x6KAQMU)7x)Kc*vhS9`xw6oB|^>hzZI-yi9zRfRcD zFs;)~>YV&99piu{7&H5#Ydfj@2I}<1`#%t^iVRlct#LovO4X$N*o{5P-P@AQ>{}?j z!Bsu&*Xo>-IafUYVW=Bl5ed-(J2+D|yQvu1ye}r_uSDrFC{CjP-PstkE4OKha(p<#wj7j0>HvOkAII zV$(3~5Y%ondH)gf)gGv*1q4FCcqN!U=a%TxkMk7n9C;GCbYplzLZHI6&S`mj{Rbw# zk^unUD*~!fukkA$O7pwVggmM{ODK^4sXKU@Y`Ep+Cf=AH&ylrM>U7jYN^n!>c z410?ernkdGVf~O(uxe9i**boGTsu~HR{~yeG~u(xRGKB_#HQPH(kY#FRk5|y=o+{b zE9uZ^ski+0?x)VUo6XrBiSbCFdZnd|Z56R^`XHPCSLgZoF+@x5{^{?evi&Id1H&pdKA`BW)!y*<0*A`Isly8dc- zal~A6L8)P%(yV)C({3h08C*ICL{R(qT;K})dx;}Z)fz+Dd=ueInI z0XzNC$vk|)VVEiVvc7(|>GOBWX|dbzdXifp%MWKw;n)xAVcYFWt>8v~j z##ntY7}1#mwadKqmpplwCb|5)E}CH)r?jOhuaxd zs#`WRa@)NaT8$~ZY2UI;cy6*;Q0sQDZD$gIil|KN%34(wE86b0=I`VuyjnPMv5WG+ z8jE)zRl*^($Na}@movI@kEro^QacSwXngni)pt5eytzX=<1pNmBd!`5ica=uckVa3 zm5H4FU9$Y)sUk}Qw$?u%jan+aRGH|lu%t&QI20e!I6n&A_?R()^+rUvC2M5FTJ=X| zXyiNb-t$Ml4;BWH?}|1HCn+#rSWkU-B;uXB{xLoeNbH}@+Q_Ft`ufL9pTD zs;+S*2wsXohGj>;!XnCEF{i-6W3x1iiq)^#9sRy(m|G8%zz&1E?WC|bF* z%ESzrSoL`X?7 z%_4TZv?`td+NHJGygw+`fOI4@a;bJ&oc2Ih2P&~*JSrJdho5L$w~qfJHYeVjYIZ)I z^QWatYzM@1lQLFT!92c4v1Lc7j0j)7dF7n6zyNMC0UW3GE( z?K-Cf;H*k4n^T-|lNLP#S|{tAEVf^+LFe6?4xE|*=(2F`(}AmJ9WHHA^FKKfbKm)o zcL@>7?$M{uy0p-e`ubLPW4*`Yl|@rilbc44pS0+)tcc0oV!;vbD3RYk%G!b>er1#J zHeki^b^xcduzy|-nnoxy_AQBS|sCb*sc&1{wtIG}+}%w+j}$Ur_}%#Ull6vYaH z?0jyAC&XTGs;WI3oc&^ePfb==U(3XRbb^ueqD`@s#>{yRynU;~Cft zJV#eCY;$MEf6*s+7&#mvlbNiguq0a0#p70L2;sr1)lN;Uh`%J?mDd0Vf_1tVBP*V~ zMe26355G&uBR!GBE62Te$91WDG)(OCjgbYHe2p;5yHl&gmEh5m-Ej_)K*hS6wFHVh z**apO%nWB+F&=9bN>NcGkA@|im T3zbXV)8E&)vMbu3M8Z|41D_?AlPoD?TZX%X zPfhUPFN=71fseW0DdV&&f!&?RTe#*rTAR;&CjF7#dgo*g?n{X?Xt|~kb3@7v*vYS< z%eAD*?Xj)c!@2%FvL+Ezwe%!XA2)er+>xi~aVSP;K9z3PKIfds>&&3N1 zTw=XBUV+~@SMvvCKh~em_hOiL6vkD>k4et3#{wwoAOlAxB>wz$vM95s9QJfO3a;`2 zuveaMj3I1vdn<}C5_R{#*^~sE{}So$y)Y3q0Rg3da8?U>!?P|Swq`*PB$VlInzbpR zczqFQRPA}I1ThR>mD>O2j%m=r(a|mi+orB2&l_D1Y{$Lx7T*e+ z6#tmL^EScf+Os?2>V9YQ_@gU5oEY{isHl6|>ufCoGApGAOU~6D5dB8-=1F{@scpvw zJ$XlK2fgt81AhSh5>hrHpmZ`_mx!O}m+-pXvSQN`S1 zL1n$8&+M>QAh{GIAGkHvZdU?qHAk2i)<>IV8E{$fF;JhD(NTih+YkP$KYAZiR zeHT4$)^(!nV`_I(L<~=fY0u;Zey7#gJB{sKTx+Y-6jk zD2#kRqF~(?6wpJCAFGLe;S}z(k(nLR;tfp)NGf-Tl+UI%AiEj!tRhiAT0p_0+4J}% z|L*oN&Q+y5>1RR%wp3)A9^-Jledz7TWqgAae@Ta3qtpnmN}TAc>7z8WTT8T5-BFbxsn6zF!k39YNE6EwEf3bbxsx=r*hQOU|>MhwONx z?&7Z-DDFRc!8c}_>tCcLV9G0pI9{+M&FF|cy$I9eEV>HVB%a@y3!d}B8CLq?Z8Llt zmQ@2hXimf3GoTV1!F6lG{+~Ms#cK}3fzaxXwk_%2fM$O5{q|c^eo8D$_vWp49!Smd zYrxsbqnrHb(+1`rm`2oVL*%fW327hPQ%7w#Iq$N{SR3bzTJl!$A zdrIFI9#FE45T*XuO7ke8xgJROBVj`S@L$kwnFN`A1I!U#fQGGgH$u}QSis9I!*F2* za*J0j<|>I}pVC3ix#|kVSd#Qgi#T4hzhJEcn#o!?xEWDi3?y zhhKY@4#I!ZOoBjUyjs{fO(;63ba5KXgM}Y*l#~+gu_$$2H3o#D~Y) z0JQFZ!{l;ld5NR$jKdM>Q=|mYcrbcD?>C3&=0+}`qSQ$&-D;ub1;~aMn(v~cjua)2 z{zhnO@SkSazUMmC;y=`^aGJEoQ4lAU9l_x$^VS*j7%AN!%E*zB86~9$WvKX60*buXc-;p{>1`I z%6C+;(HXymKZVS(M2kTfvMg`ExfQ~}BBAkp=0LhmXxe5?(`Qv{PBH|S83c* z1>|XR{tRmdpZn6b$Kr@k_d`Gcs_J@iYeHq2AF%o*Z4R}}yLl$8M${`$p83JVyR`6y zd(3uPjfW!Ay#-!u@QBmnhlu4@LdS! ztG;aIx6sa5KRpljlYqq3Ompkq{Ou9u=+GIx za3Kh)-|v4D=cayxUP(Sx^niSA(`{t7S&lvY-aQ#6j{+3g71STueZ~HL0+wfuwSrkg z1kRwJOUoqm(c?4ioXX&HS_2CD-&j%ttdbb4x+%gx%(Rxblb#DcW8waeNpZ)zp%XAy zfBW3kv&T3Z_A5bd`ZrHA{MJ@jniJ&nlI4AIkGrF8Rd~3nJEoyp?Lh@Ul2YO|s((g3 z6wIFZ_fg`!*rqdW8xfKIUn${x=jE|PbpgH1wfOc~Gd17dB2y8sq6^kHtp!1W-&I)< zl_--!miI9zZ8n!3aKBYT$#nn=HyzDEm1m#2f3yNzRgxZ>9)^J*tpH{YBF3A?L|uf` zrTmS1ca18RmH*MkLG88%UQ&^sLu*=Wu+`R8UcK;zKintMT;M5-EM%Y^x@@_fJ6E9{ zjt~6vb7sS{xZ6vNR@!ZntA7Qi%zw5JO^LuMyg5@@=?uj{Cl1c~K2tn|d1}CFW^`$( zm56jdg@B*7n)Eh$WrDn*HoxG=Q<^#6CH4Fr#f8=-00)VW5ms&!cC7<>c?^s!6jlx) z-}}LoHMuUNiYV8r4V&gf=^^VazG{3I**ecP@4dmf{G7SHwDY;z+wt~R&(Y^+W^-{j zonsti1VvN~T7+@o(XXE!l7%~9v1ZttFDUV-4#&e#N_Zh6dZn%*_2^6%;Ai=>gvz0v zVr%*LyH@XeK_WvW%rXi9L+xftl%bN1IhH@RNj62o^24G)MC(LwVU2(H=eh*80obssS@w}^{*9F+30h5w-4W{meeN9b_~LEI z@nKlxh%+#m_ZmwUngwL8s)1j>g*6k9GXVp;84&AP(Fg9ArZ2mfPUiR3`+LT8ZXtLF z)=oTrxaphJy%;7DGovh|@lvix@w{+h->vz23Z8ehCa=nDV*P`oc3X!ktI9Tp+UkZo zl~^x2^B5(qSQwF9Sfbe3y3UVV*D=LchN$mcy6YPk7P*9E6x;nc2j6J7Z9d2}p4xY7 zPUcP#a=za~TgJk=Ce<%vwky(|yn0CMWTmBon3w!{^HFrgrvpoPNPTzSK7=ae7dy7* z@_JWHfBy{+Q-`G#=^4?$wO46X%j#&Pe2<62DUF@a*W28xwxY3pd6Lbpq&DQVjwh)JrT#=Ft#vfy z!}=4L3*qL2?vKwkJal50E)Y`KPnhQJS?|aTX{VH`E7qPcud%=y3&{%QisU&E^Vd0Y zyI}_Yu@|5(Tb22gP|+UZ?-jFYoCJ}Q+Ae_|mmP*JW}0wcdcm79hcbw4NV4Z0l3`{q z-aC%>%K6d$@y5yz<2om2+6T1_uF0Za3FuM8BLQu>@a2@eqQd3Nj+H~>iO$wO-=zG{%yLEr zz(i@k`S_;vJPVbmCI(&@+$=Z?vuhO2i6M#mK3(u148@{uxNO=q^rv-`}~b**^ul%fH0^ zq*IwPkO!ne7Kd??B8bH^5%QSVpPF zCpRW6`sD5m!*t1a9C!GV*V)p+t=+ad-p)GOSW9^GsmKhgiy4-*wDfci2GUtLSn1b2 zZLs?jw|f)o4D=^)k_wJ2%2VqoOwwPY()Up~ຼS$g4_Zk58>bGLS32MAblPiMK z%R)H+(#&fl8|pdqBmh;j#c0~UDT>J}QviqU!<)mUvV}=9KqGAJ(M`V>lO@^(-HBg> zF;K-@n~br>B0rQj&Lqu(uDl3pmYrt0dKb*)Ap^o`vmNLD9Na_u+hjhhAKQH6$>%&e z28%=LiYatQd0TM&ulFIG)${Pf8%^v&Shhoa3_3XjOCT@$ZtcKzX_P%ZnQ4Z0*c{Cf zsd_*GS{=rEYXksZ(tcnk#jijgcM~?DJwAqn^qE@0F5V0jS7SW#Kk^!x&OF}8^|#;? z4x#gnW0T9qy3;79ncpgnhac9-QZj4*w>0a28k9$~lLk0tKqxy&uz9v+`y#9k%Q7+G zAz|A+xUzf3LsJ&+M|5v)_~k#p`JWFPOyv1g>sALfMO zbc8m3EQ6-T13DnSL}N8@$~?P|-Fph@DF0SEzVygDZ;6?9H`wB#@Gt z_?MkEh%{W_%HVQnBRk~Yd%C{}d{_1F#HCX@3daT%;s%V$0pXrEH{1F5zW@ERjSrYf zGyxWE!&Px!I{l~?L_f>`kh9+uHE;bbS_YiBI1qJz|Iyz*lY-#7QtC^A5*khH%IVz1 zc8IH|)qN%f8lyXm;s%tkE#@coyFNGh8zWZXhmBY-MBlwY zKu3KTU7qnsFAe+zJI7!zNm*^|Wpoj}Z)G>#gZ3UlNcUS64FA^G|N1D8UcVi3d_hUP z(LZ(q*8Z-O&-ky&x=M@|tmj?DoOI9z-382s-VF;x3{dFfUBo*h{@p5Y;$e;JmKV2M z(Y3Ar^`8I!0~j#)Z7lp&H2sv0D6^0-*ousAWEXREajgR}>G?Z%r{!;c6V#7URUN`b zJQI#J&X`Mu2=pHY!KneObiY7ThA{O;SAGlx3p2@1&W&G#LE&&b){^TpdPe6QT1_3@ zI`TS139I9Isn4o6V)Gh$9dy0Mb2+z#1$J7T0{yt{&Ny?WyU)AGFARtUJ`U;Bg|?%5 z5*YMLj+jud?{h z{;q2$n93<~jO-O#=GFcE4p%Dap3n1Ji@1-d{oJ@|w_Qqq&qc4q|FmDRPAEi;6bXl%Pn7z z$LAy^IC2$k=X99tDY~^lZQ8b^eDLp0{MTpFB%DU>KKVTV$j@pPVx0=Nw~GzZ)t={H z@~a8^IxX~_VIe8hugo7tFdfRf@{(z?WA^%TK-h%4ZLYh=;`5Qem#O-%h7l^y5{L0&^?!`Ngk?s$6dXi9LWhik&3R3BJz2`2y z*I>-J!m}?-T^$Sb5IdM*T0D{5qR69W;bYgf;Q0BP-D2Ryc~x(hJ#Ix7JxIu$Ouxf# z9(4`%4W2!%%a_|Gu8f!#{cz@?-r9_f)^JimLZpd?`U?Vm%G^CyU$joI6(lCFKB)?Q zOv)TANm+J&F`)dE9j=qmP_o?@wp?>ZGloao1Wu-p^GJ2gw>mkStWV_>CT?7}@ZSRY z>#qfQP@(X5E$NW^*5ZQ}w$exX9b{+Cq*b&{Y`Zo`<pY9KJ2b{rMhkPVB|1iXb1WCzy#HkF<#f zZT&sOhqZgOO%AFLR1GdZ-Nay1{gUaM^l$sZ7FGS%pTsUXIeR-c zshe60DaQSu7XSL9y9aFKXXY7Zh~eYY>@gecHfiedFqe{D(vK3*}S!`+xXO@%)>-%ZcA zEv?%ZmIo&Yz#faamF-28{kGLwdk>X-p?;FPog;vwm=;ul;f#+D6nWUvlo;E;Cp@Xj z`|!Ul|MhodY@62Rwff71NR6`0vy+BkEY8QOuF>1) zrKlNEEh6-v_k~Tnug(79Pg>op$E4PZDIMQM)5i?=rgXG_`e#3dAkDr!0Ut}c8QvS{ z8Q7BM9}iKTU85YH@$qqf-L!XpfZ@7pwjmSJTjCAO6}qyw<5XO zX9(RMhN)1_Ku>6WKW%i?Cp{ngpf$inA(GS=f`{2Pa}(Z(JJ!Qi^E!hCo}F2GNRgSj zBEl&)zP0OzESA`*ZwECp;oWqqV;hW8Yc)({)fES58Ta8{5i>iDkf<66j_Ajdvy|0RMoDk*goA<IgsK*n_c8tK{N@*<{AZF9nS4*QG<&$_2x(IjSpNMFrlyf zP~J9r9uZ>cx%#>PGjuuNlWaT9VgpC~L;IwlpN_Kb%zv|=UZ4|^e`L9)Tc#@!W{Ps$ z^xPtKYs>F#lDF^^R|W=jl>+A|FB6kFag(2q$2FNZGSNE-(LG+eOB`6!6VaW$l4-~* zM)j4uJ-b_MZCsF)vmn<$RsO~x0ZdMELzzMSg))jwCb4wK8I}IGQ@>m28H~BERVFeN z-bVE*Oqu}q58Eesg;v^ZKVNH8{D(nodocqEgzI#PV zB8A?2+{Tv!wDYc`PTuu%zqwGw*ZK>~UCJ4^2VXbs3wzdpcoc*8o}d|Mu&!@}UqP%F z*?c=)#~Pl^JxMgA)RDPl+)43i?6Bhd$ZdJx&WRn?|US76)T`Ez;(%g$E@#;#fn%bH!4 zY(uAIv_sx--TvqK`BRIePS-F8rOX|qib?06>8xt;#y`q2C)e)6!=A7lb=~)i(hy_U zIqFZ_J?)GIp^zCU6GlB8)v9dMJ{0~ES{QSVo4z(< z{N|Z+w)!RFI@92U`wcSKqUUhlKgK^5W#p|U5MyiSj;)kyADchG0>a|D&kxuKJyQ?x2L5EKnd$ z&p^TJ^wE`T)y0@Vgf}?MX_K@PV^FQg*uc#j3^Va*P8$YP)O5G>%JZ}~)R>|jp6S|b z8FT)$EG)>X)nH4AY{gSg!M1t{@oOdc2bia@YkAO7Ttl=7JO|fc@hp8suHT_?AoQ=` z;C91MtM1w#s!mRf0bgRWs8v#4a2i608g=;a?b|=&iuv|&&mHMM#)oBy;Yu2)1>K18tj<74*F1IaT}E;guxL|niU+L zB1B!pjIW+k-~bKTHZYkG&eqVWDlIP8%kVamzdYK-5iMe`bdIVKm;@PT1}DKLK+dO)0`5GZ3IZ_}U0V)Q7SybtTLd!CrRj*`f(W(g6I}iIClu zYgGP)u+lAz<^M=UxTCDRdC-9L)E;-gV=jQ?I-FxokQ;nQ>|;1no^a%u{GsW*6Em6+ zB`(j!<5^vHPn?t7lFcl^JKigbS`s9F?2I>FYdgcxn4FkA7d=C5 z=F1FtQWdueOF*@nQns6j^}(q;ESd{}xAURb(~?eJc1tgBw01C!YI$;H9G`fezm)!@ z>V6z!%7x{^<;qWdna}#MCULC+>M8o+CQ|TswN5lIhCyS=4Gi&_SPmdSJ@bsq4s_mV zuxH>wyz^A308)p= zyq+xycJpO-rD_)GF*E|8fI^?YwFlOYJgKV2EG<4L0fFV=PpX^3BikgM)|9|GDmibd zh1pWv6|u6DI4ddMdxlwvsQMhTL0iti2wa{{-sq*h7`;S1=e{{18Y7>q7&KCYCQ!Nn zvgF&2A>YRZTMh{tCKi(4qfsoD7POW$OV7tEnHl>?4?!XO#hgaB6Qw07&5H%>(WV!LlIvl*I(y}*kp`x%Ava!RREddC zGY8a1=F8ZyPb_niW@a2j#Ona>5?1OiGq{btV2}0=j!rPPO9>bNq~YaUW6LM65&@{} z>i119!W~_%)g3fxB8pgcVC76yE{NBGT7O7zZcD8|Q&p|&@&Tuc-QXJio^=^9sw7vD z6}9r(d1a}?a<^~)gtHHN%CLmO%-(+-J$%UvCwI}&Ck2fPgR;tvo&Jngp%+jl0IH}- zJ!PaN748sB05G*R!woh5W@U{kQoBZ(-RJ)DZ~xjd&T`mFS#0fE0%BdKG5Vvb8vtkf zAfFGoHpf}&+wO}wO=NZ4EmDK^@UXNPp6?*j6vWyrX{<(}GQSX!09hXBdGzP*mOmXj zPtLX9Q97!oUsb@@qCum!i!FL1;#uG(v%VfeP!cKtOIgJ0ms@$MZ^-)?QW~%E++Pz^ z)y*@0CUy09hdi)~_sY4s2ewCRFE6@v)n6dTGNl;1OF8ym_KT*B)nM<kDfgyHk@QNSsjs5P_2zcOY%!=V3WBY*v(HI!NW+F0ixmi|eis6)B` zr&HZbCpJNNMpK1D3P<9h4f#(iol|B9@r1Gn02?=%Q2d3C|K>OKjqzjUCd4&g9MbC3?*5@Ixb_I(`hUGeMC!#1cOj9Ku+ zZxo$VM@hKrDBsc9x{GR&YclB3`LTu;w5cdHG5IR)mSMz*{DbEX&jggHbzrHyxR}64 z!izA;s*hu-p7#)Hsn@|$mxY`6NS_U>Y6k1y%N_#L?10#1K9Q7Z}(RT4h95Bx~wUoNn<7-ufL>^7eCHOe~a~3joJ`kILbNbF5uw0Ub1R@ zl6@aYRxftPnl-SK^A-sx9n@{Lh-WY~*`i6EZeI04hH|j+)p1!VADyn1VzP_Uw^THD za=Rgj+}(&M>u)XYru>dwEbb-L1C_q<2j?@#mhz$hkW;503cCHa)osL-$ou4P;v!If zsPE<)Ow`Aa6;XHIkIlN$gv*rj!rCE5Z3f1Ct1$o^w#iggMhU0C;C3DZ(`gezc2KMc z?-qf%+$X06)a+?htK{$?MZ0A{knNEE^8SLmrT=O*)gv4xEhd<_`e)B`K2(^F;#^*g zNU|sb$#cby8si{R!jd!j_2rYQ#SZoGqwRAq+`AN16?c$6;EoO4$F6G{4iq0M=Ohki zp=NPGuhYfGy%5I6ax(4~fS?e?h= zzmJ(8Lf$!uFhQSHsAGq2b>b6@YdAOzU!#!i%I;r2^LvoV3Xt5YE$hnNmJ_(iY~s7U zdo!VS5e|>vd;&xnCIJ(VhNJ!umQN|xPS-H*ZoYzmPtbQN|IO}6=rc!d7AecV&T;Y!NxBx#;v8Y+XI$<(g^Oe3rU96?LY)AaBYr39f98Giqzd>s5!{4L zu-eTIKMM#l=Oh(qGAVPkv6>1_ag*}PJjc2wHoE?~Uq!5SqV{xKMC2U4FN3`YOQ+O6 zC{ax99=jxpNHfEnCNGGJNgfDv&%U#=DygzO3PQzAj{M34A(;;Uf3m!<5Kg4}<{`Y( zyy$cycFD_e6f~e~RYd95*z2EPb37;tCL2Y(Wba*XW|;ymdqnWp&;8oYlEGi~d5)<$ zJ!0@Eo_qepLCJ}BXtVd}Ji*juJl1vu#1CQ~WI8#g@~XwIz$Q4k#maoUmv3#k)cOx~ z)pO-LP9{7ow{2rvatDl8F_OmZkGp9Z__A(%rs7G}P(3S_DiKq0*jg)nV0ms+;IYsB zyZfhhi+PdBAy1F+*ritEnR|8`;d5%qdOkwcb+kZ$mZ`8R-s=TLZ zuC;5t5s%e=_sE7aueBACWKQN8zcz+LfC=RH8SQ76xo{MazC5WbL7{ci8E5g{x2*ot zf|miLG}ebMvyn8d(oJ!b-^uCQ^=@=@I>sA!&Wv5g6wuPI8v+UfMk&D3m&^sCb||Q-fPS()IE7l5|S_2S1nuqVNp(OuFbvXUlc10Ya<~0&R_E) zUmF7_l*6^u-N6xK%F**B8#g0awjPIed`|32XMMxkLMK0U>6NigtFHw=RS&?%=Hp#_&iD|%VRr2t;=qD z|D?*yIo-iFvd~cgm)<7y+W3c_2o^tpsQ3Yek06Di4oV}vFVs$yX+{jE${$*?8|;~; zoRdtjRm@S-%syuXH5P$2)tre&YPT%CYX`YoP{-SV?t@^CO)%FwMVCv^Q$_CoH@|%?r z-Hj-1vhwkOLa(~=`je^#<}S<(G9?nGEopOX=`Y~eR&jKy-1&MD2)Zry z?*d|DPL%0zZW~bY_%%;3WWtug1`sO6gl#o+d&)gJ>=gUwM+K#j8Mu%7pZsa>riJ4? z3HWhgqaV{Yl5K;oJosY1A@`Le$$W2oZ}%E(0n@&ji_rRY*j7KOe$L{;V^N7Di(dd- z5E%!hPomCLpYsOrRKfY5-qa3S8EckZETd4!E?-BoUX<>Z{ndRkf#RN%`kO* zX)@mZy_)NDK>uUo+P;|FcQ+sE*d9ZV`HGcNwZQ!sqOId8ONzybm;gOI{Miv(_ZgDU4#5+uCkD&AS@8{j~5a6ICv z%#4ui%H04-H!n#KII}{XTjdI##Q_0#7L;&9bDK05N{oxn%w=A)pU)o~*VXBW!FmUE z>CYHtTQ>bzsN@xZQI)fQXe`5BOXi4MOLZp#?!GX`WL6iviN`HI^{%lsSNomf z#Xy*?d%ZILp{kg6c0_D2iy>-H*NFP(eiuXktsyI;FB%rM9bGA3%;YmsTt~-LH5cg| z470#FN~vI)w{`Z%TZ+BX#ESs~RmI&{BkC6T6Z7mIV2x8Gd2P&PC(2#QGR;}YtG_!8 zH!T&_wb;ctpxX4&QdhjU;n%J!0_my_QjE2nC~2{Mx#MNwgyLXj+`+2@1|5Zg?tk}u z=)R&5!HUp>E(^cQ;A;im`^w+AKVTB_yNt|toNWlh^-VtdJ8!`MP_I-o@*l1k_Hhon ze_dK3wxokZ+D;Us=-JQIACzNYys$X1EVh5|ynQl1*xu31|gHS~OZ>38p>j#;+gO{A{tyF|9i%-|msttXo<(?<3@Z~DTe zP%LzOpm-(!WKLT%=DGT6xK@c;mh-3yH(4%h#s^{}@?WY+`=Rd~#be`%I0bGUo&I7?#^iA~4-c@ptq=T!!`xkpkQQOL zO?Xj|P{|czBRND%FR8;esl$xm5k9oVloK~oiW9`F!h#^C5afxy=f;?8&6L_F;A+}L zD7i6d7w-;k6SJq`o58!gF8mfE?VacZQkG^)bQ-`-e$jHyQy|ppYTkeyu~?4^>Q`0B zVx=o4Eiy>8W>Uv~F+g<`zO*kb!#F+q??U_LI)=$qfa(+n>R+Rj8Rc-5LJ-g`YB zH`B?P+SkAqjD=7EY_Klbv#?0Bdn!)4C;MKcyt|BqgIY)Qr#{ei*4_LPT)v&^3?bK@ zr?lY&X^lCLLi}P}r9HgH2AzW~RwIhLw#Jy2pc|Vp9@_B7)z!kyOj_++Kd-b1n>7~g zpfHmb`8$W8`h`>cp2frvPnw($o-oSkJv%hN_~V)P=mo;ebb&BF0{>>cS`CYqMi4_b z81%F2yMTQT^(;|&-CCG&2?;OOcMv(I_H5`rfP%?-@ecKrwd>?45nIt)YiA79xjZ|( zfAB|P>o7VD^q`gfND8Xfc$OUSJe8xIeF-u2)1QKzLb~p8;mFG#y@1%GtA`rwRHI5U0TSW_7Yh-0n+u=xeZ4HxFm5xXj*xa)?uyYUTjq({sz2l@Jf!wEa}!DMlB;{X{=1cPeiaiqF+$l8{n<$5dK3k98~5gQFk9RS4gPo?S`bWDXxx(J z@Yu?k_!`MDPVHMI4|dI((r&RC;BH1*Ia9ufw4TC0AZL+XC|_cf84T3fRvm`!Vzx`# z)Poo2ouwNdg?Gw7m1AUd5IJBZ;kq5SM^>oBM8r)uhvF67 zJ`7I zOS3jaZ>&*wR$xnv8lNb(XEWqN?cQ>isYaQR`@M@JrLmqaQ(_HvR~wKF74`aMIGt$j zHr3@}d7TpKakzSLwoYgVh;wG2_$;P8sS5xus;jwu*9mqpQIDZ^?h(yTV&|I!D=b2@_!5ixqjxF6f_ z39!=?gt&I8av7HyJsHD*KT&ozsbg$nm7SgKLr8} z>;dyP+2YvGGEy0{A1xybm0GmykXVs~R$H%S&P$cx&sXiX1#~UQh}Ep`)yJNgT4>1L zn)8TLtalG(6N`c;o2r=pl_!X_eQMvD@_O5|=spvI;OkbC3bmTq4DaIm?$$112X;n)%-8*K8X0Il;)V$ou z7JT1(jB=g=Y!X(JBuk(Z!V@@qYMa9N-BSW@NhV6h4?7dQ?$)E|aq{p|#@@ zW>So3ZTO)-hP%9JEhqx-+sS(F-aZE6WpGBg%pJ!pDwgKId_PaI4#4UOT#kmu>`eWs zYlMQFtKG72lh}H-qHazbFE3K9F0n{l2==ypJ$hz6&GosJvn3{6LZmsQ#x@fh%lb*Q z93I>r+@zj_YsV?Pyb=;#be;nB5l@tb-hQjTO8H@WsnX&pW`~fv4VRFFn5Tq#D3|>} zgkck&KJMm2D`#srMA{Zq>AqUV-;&7*_~2if`9Al}3o*C9_K_-H_<%eCrI+yG;O`Fq ziiD?$9$Br~(vtaDv>e-*0-FPtm)(~cP}#uI+TSXu+<6bk7m}i`??SZBOXP9J73Zos zjq0HW^>11VgzG3#yGC(j+MPYO>ilcPdyi-Tgv2L=di;zF!)01{j{c5aECmUyKOMke z=o>1>qzhr%Lj>9>RPeY6IgoM9We}KPiBd3i6Falo$Abf zL^G;nW!Z*&muF6Zp!o7arn)D|`x>VCBhtcB;}a99Ohkqa4|1QZy3F!!P+cTTE5}IS z2oh)n;yn{%$gMmB-l`sp1cl%^4>NPet56SNTvwY?22+e!FF56E3yAdP)V?h8!iNXV zaUyzSHU9zvonTKf@fg~T$*d?Qvt6D?}e=O896wfZ2$Tt786V?x>e3mHrz z=f-q6FpF&>?@(#RZMf{hRVHy~Zrtkh@S%qB#?v_&@4Lseybpklcj58=& zX(p6KTO;8l3VHBlG7pTMor%PbZQhMaG$)In|}+El_-O{`ID z(B>-u$%mK=jtF%c!cT0-4q-ysZNlg?7L|x~ZrpmQOYV<&&N$7o{SD(zq61qI<+@Xs zPn6x~$k^2d$<0;)%>G2vPG@b}*vLyd@@i7YI@}0&qKqP1^JUs?J5G&I7SH+i z-6PIw0VG;Csl#%;=4zQu%@kb^!TVKf$J&yD z?XLNUwQJMm?{j}7gvSV&7pV>+J8GWes^$nAZWYI7dUwi7R|0?in#1Z?Ym1} zsJzwkA|1dj_+_j|7d=nm{p}P!kX-z1tH(pNgjN%;N_*+{01MnrL7aPI=JpM7ND6bO zvyxt0@0#y=x#x5YlhN%^E9c+(u$o2a*3_!cN|bp$C+4sB=c>kQ{o`2>(6zO@5i z(R``0LY=LnFi#cMxgm2n*ze2GEpw@I)w%zEPbQN=qLU^Ic9_@I1Q~QSoWhgIBTyB8 z*MiMDAdS`b@BHI5C|m6mVy7*VJ5id(L)-qIm6XWesqOXlsMBCF0Kk6<2z4%&!N^Lq zsIaruQF=>Jo5qTY-?D5&TCv)4^PsbOWW>NMa19~k7^BK${i!%tCaU)l*)2|^?f)C= zR(W1j|Fsw3|H-?1Xk&8dAZuqN7RAJh$t#TOm%P0r$KB+?WrG-TlYl?SWQvJM9V4Wd z^QnDp`ctle5r>!e?giFlc*!z%dn8U5%qEMzTV$7%R|nMrB%p3?G$5P~nm+62l1lv7y5Z=z`|_T;1YqZVhA+6R08BJO|io*V$0EHea< z!Ou7gAU&Aq9OTR#L!?dD0;z1;y{niz@{w{_7ACQ7a0TvqcZ=Q8l>YVE>_?n_ zTyEY{6G7d-KkZKY!&%y8&6pw5QmK6x2$6;o;#0shGy_wI@pM-KR-hAsyX;#|t<1cE z2GoQjK4^%ZG5dt?UfU1=iXhw|wIvE)s>}t17!_C@1EK67w#fd9UM&&N*r)TCT zrHX~O#_+DKIZlj3;J>M@%-RxGrbd^S<%Tb9g1Me$Wta9J=d0*eP{?l=9Rln~S$synuZ@-rYu36N@OQpAavAB-)tCat|ZxwKhH$^1r zN|!1lHmijh`a!3zP8LS&DtWzL&%}z z84r%H)24XG6`fT@=C2=X&0hG;uNAk5aMN!mb+o>~m=I9lV&kaeIkCa&z=cH%l&}|_ zk)2;_Z0BGYf<*hv@fz1gzX@6vpC-+lRw3#;evzFUB43PwS~DLvU9w@k{r|6R2WZ=O zVNpE$k*+`QQbeWD$}N)zqLzO}$S zG^FU*6)cS`yydh3vg9c~P-gMM!pd($SqoYQ>b2f-_g~(-#~Xc3twn)*3e`c$e;WMX z58YRa4E{X&rWP#uu%E{CyCi>@Zp4u?Grs#1=f)%tT!loE z6=m}(qH#u!(BXTpimPvor4d$g(~7-l@SpHFb!Kp*{&b;b=2`4Ipu=~VyQ+`{59t0V zVS{L7tQe)55F#J+2e)wccHp@7fvkA`Qh85mXpL*~!=JnRVyu}M2AB^7K2KB>XVl|x8 zSpcZ!zZwZw1I+|`JstP6Sb<5&KzkkP!H=8Z2;Ypd>2^2&R|Wt1o4$DY`F4cz$jUjw z%K2xU+9gIKx~N!i87(0pgpZTn)A>gn&RiOPvtfKspnIx+(Qkn zyd~uctCwM3ejCU<@h6C>=38ekorU`iJA=o&7jkr09mT*&obMh2s&k<0{=-AZz+5zU zV5ap*3>w+L^1gT*1`LwEix}g3Sui%?D}z_++v>Uuc>XXpDUf36{1f`x;r0_{EkC7S zaFI;y%W=NFdt-mg{~5*~UkJmJ8|f>c9L=(M`BI)GfG*WsVX&fE>mLk`vJ-}}%hPyC z45f)8@?vZx7H=t7etkjp=F%lmJsI#H34u3U$}(RLr1;EWG=!Q zuMIYe$HzdVeYyKc71L_7|NmFmm4`KTbp5MU!3wxw1ymZ*2wEuyqaqE9idzhVLaRo0 zQP5OCgC=1|R1iV2LAJ7_NO>(Pi_!!&EI|;PmQ9KXgw2Y8Nm3|R!J0^U~;krCL0Bpt8`T?36=D}68rcBvv& z?^KYP*Fg|jV=py;6R zaJ7c~6I{B@Lg8WT=JKeGa~N9hJgzo48wD*21i{!rs3RQj3E1=Gt02yk7#o*go4qQ| zyIQrd-wID2Dqg%JqJ~@;GWqgRA_+hO@8TfvP#-4sBcf_xcZCDhZLN!}EnGgY;(T2; z&}lvHf6qc=c&OU_SImx-OsO&_ zq}ybU^O*MM$a6kbFn6)H{H>A9_oFZDn+Rj5K06q=iwt|1;MIyOL^n7&qnch*7h!QN zg;7BNysEJnaAwiYVe6(eY{u@HKo;=Pcise_O$^g0fdd_NAnuM=VW=;Qo zbQ(42w8U*`F+pnPFJ=9vAz;@xJL^DpJwZu(J0>5nRjB7u0-i*;EcN89`L#`CPWbGz<~PN{I_s$_R`e0Bc87fXvm!<$Tma<7i<*~sC8CjIB%3wpiT3+7*|{Ifgq zx0~j(6+YiRFvqrcF@sDMghEpdVuj0lzY!D}laSEtT{4W;K9VXg7#$xu^Ws33R8;!9C!2#gyfj zo>@95o;j(%9CB+8&WxDP zI`1Kiu#;pj#^qu9c>>zeZD;BWb8Vz>4;(HmclP^Wms%dDs z-j|WD=UgNczE*^|{83Z=_vh~!6sA4!dPB~BuLyly&ML5Kg zmG|XLk}SxB_F2;Av-so>0eZXcEWY|{$UFJs_SbI-3g?!U9%t05()4x0dUCzy$I5}Z zPkb5AXk{z*F3GoNNb;BCR)@Lgh;_?<8(h@!tY{F0|WO}!;#3o_H5K_N{~ahqA4 zS@nkD*$C`>&2LR;2H+U8;+TpJVoRfrnOZJjc?EiOHMq`vW7?gZgK`;ZO{U;4JINs~ zfjb`GSsI*o!RJ*FySLX64(s%NlY|@c3H8BL68U?E5O9OoWZSPNPSth0R}ENJA*Qb~ zKHb$-cYNCCX8x3OP+n+)Ni>!FV=|I>(eBBnw7jnfG_N~0Rdw)}h4{xZvP!1F+zJ=) zL~n|iWY{%FeMSp@m*2NRX^5o%Ldzg)9>HD3fQ0&>3F-*tKlLmJ28H^N*J;IFv7#)W zW4^04HGb}r?Vd(CN@8B^9zOX^+aLq3_Ynx4zNt-rRQ7ACIk9OYzm>9q_CO}QM4Wup zQ7Ia;OLoQGKO6~!3}HxZx(PBBqc<2!%=KDm`_C&oNs|3T;)i14aKPoP4ScB%en_jh zY6JTVB=qe%ertTQ4b97k8@P|@9z5c0VDrapC+*b8u@kFicK)=x5j1TL$vc=S_#sQW zN<2pFPWxiS17(5EK*P$+(xh!Q>!uZ-`7=-jBmI$`x`31UbH~lE#iH{cS>l!5j#%f#!zLB3l#M~ z{hdd$LMBRktZ!~pGH7>-syyC=Lk&k9hFcfyB3Se}sw<)ZpDwfM(HGjYx#P#r>30I4 z{IP0gpG~`y2RT1R+pmpvK1pm#t=3%_y!U)UCY_!V`Iw|St?bO7fhFkYqv7)=@cCq- z0rf^~;RWh4S$h2^k9T`r=?y{b4-+-JI4bz&h^F7$n^3_puuTHWHe*x^;$!B)WxYcH zlV*rvWm0L<)i}MGAD(vCaT?4G*k$rR^@4Bsy85H*#eQ6sS0^vdsiprMH*Bt{EW5!L zmR2DMhnXq(U?(}pogkLzKlVMjth2V!keVg65MO!W=hmGma9A_#dozEE{%(|<*=hq) zVI~CWTWd<~ujbQ3dHcOyg?p@T2rA%p66ST!Me!6Lf|0a}4Qx>7g7kXUAqK^|--9G@VIV3f*uB_#wV>roy$oGH+|tgsLpY%sP`+2fvWqh1rA zvTgIU;xm7mEkx17wQj#xLZ%>tSVF&+w0OJcPXGW^i(@=bmNP@hIx+K=X?NH>Dk5^c zN+p|E9*dq!p)tE>btIV)P0u+_D>m~dXG-cFdmXMlrVpo{wTuGwrS)5vPU?*se5{aL zW37D5`Ek9+zDK-gfEmQc{d45y-ut~gZ{Qio`;Ol@I)bc?RFIjCcwx8W&=$s`r z6u+$N6S0Ns`H}DNK>&`Ahlip8zc&K8?Q`naDPcZ?Bnzu)LpZpe332#=A++u1iX1C*AQ)rrpYj4Z_fG zWy{e>9nH}zKH%ip^uL&3RgC6k;Vd=2fE-2vLBSuhVhFd6Bj}#M+@Kr2HbBaVqhsY#<(n_=Ib`rU;W0g2Awz}c~cfwBc zwr(Nr_Yhyqx~(iNic4JoF_^P6Q!r*HapT6wzVib=P$Rr5J{X_J6W3titl^zqZashU zDR~`##nXU31X=Vab=QvXQ}w}ltqwBic54Kgc{r#9Fs4Exz7Tz$f3McVh)$lgIX5KR;g57vTv=ZOeQp6Gjp55q(<6b_3EI@tq9ip7<&@ zb@0#ofNu5IFFK&@X$%8Hm3xU;@_4u*)u2hs693KChoGFT0otw6FvlY)g=TC3zR0Cg zRyJ6s>Aca@kG7|J2!4M#B=m z?OD?ES6K_vL-IG4b>9I-NK)VnooG$3*VYx_S0Wy6;K+UvZtCa4(3C~Yr~j|1$*-C` zrOM-}6JGe@w*TnY7zb^Iz0JyhZKp>kjcC1aP0Rsh7-Kw0s2ZfOsbF>9n;&G0TZJgT zhzaJ4>mH-)&#YUn^2&Z!zu0Fd*}>&;Qw;o$j+`h{Z%b(Yguj6w5?W%YfQE=t)MgEq z(4%!0eTYa}$9FO?bw=B=x6H-Buiz#`mr~X~20PfcV+@}k;gqEOrbehN@%n}K(HQNn zj@(Sa0$QmVa70LSIzj5H7IZZ&x1wiM3V|5hHo1mj#snQYb5We4iD6C8@~YXMw*p*N z54Ci{4PF6xtn};fMnCRf#{R*U7yIy$Kk-qCHb`({5)F;<$6j=0N0-W9jzBD8%%!|` zA$NS+1HvcoaVJ0XaXLeoCPHmRoU5|3%_IK-WVn!4xN|-cmbpK*xYc}kUf4+pj3pai zwg~)GGV$+UvBZ#qm73@68Dq_P#`qM7 zh(()VwHL`sYmfWwYBc~7ysHx3ixeSIG6fQ%NxXYlInFlX==-5fQ*!mbl4)HDHcZhW zyjr2`s3r;HW7K4&IgbBSwbPIR;hG3%+*U3w-en zb;8tP?`1p5H{#SSO}90l<0lLtEZA)Znn|*A-MB8|JT=2fo)GpiC(~q3t*#b+-Hj)wI{G_m zzbieh6kO>xH)TDdk8)jait)i920g72`1wkPl0g?@238|AQ;<(Q((pDC6fnygpBUHq zQ-i}Y3%~a|KsTXT=Cl~U^wHT^Q#~<<=U8IjAVgAMU&Svkvco(VN_IVK>zw=9l_viC zn7te&<+)gS+F7&k`>!9|fVqzxCBttRi4FfiMd;8^nSQ^58VS?}#5;QV zLVGH4RLCFw@<^t@Dndzb#`na%1lKup9Y1xVx(=FI_jbd>`EXebM{lci4onjp7F?-C8-^ z%Io+TkKDp0ud%^};SkRkjLe?=F9Wcuej=bFP|RtIktXR*14v6Y2p?7+Kl z<^>owc-MAun|}}-^0WB|iIYJS?P32&OFvzS-Hu?}kF5LtB}ulnbju``Fk0H4s_{B{=81CMe({dKpeO$&7l#Lh(re) z>npQ^?ezo1C?YxeKwH6GsRoUpq){ElD9RwgR_*nDu_?q+*8=LXwMj?3*oT#@DTEVD z)}qiSR}Hc!=#MglRMP_wUHGjRl(uRd7JEaJ_P$T{HxaKEtE?!#`vbS^uJp#2ZTMY* zbcnG=ji(o9EsPE1zqz;V0P`Tf^`S_p1W?RieIk6ypw?u_o0~x-NJm%a!2Bn57XA*> zXdDqmf5i4TpZ62sU$!+3H3hCXJ^*6q^KL>3h0bv(*oibhB>NkPOF15&f;K(3ax7*D z-^RD#dPtzcxLD7piM4lHTK7Mz2(&0^|HR@D4JFemY@?H^a8{6ta*VFU?&JIe)ea2hM2hiX5i$Q;PR4*9$H*V5!n;UV)B@3bWir$T6jnc1 z_sji@=xK6?A^b2u5P#`Bh%#nl=DB&#eZhV{AdB::from_bytes(black_box(&leaf_bytes))); } -#[bench] fn serialize_trie_node(b: &mut Bencher) { let node = Trie::::Node { pointer_block: Box::new(PointerBlock::default()), @@ -39,7 +32,6 @@ fn serialize_trie_node(b: &mut Bencher) { b.iter(|| ToBytes::to_bytes(black_box(&node))); } -#[bench] fn deserialize_trie_node(b: &mut Bencher) { let node = Trie::::Node { pointer_block: Box::new(PointerBlock::default()), @@ -49,7 +41,6 @@ fn deserialize_trie_node(b: &mut Bencher) { b.iter(|| Trie::::from_bytes(black_box(&node_bytes))); } -#[bench] fn serialize_trie_node_pointer(b: &mut Bencher) { let node = Trie::::Extension { affix: (0..255).collect(), @@ -59,7 +50,6 @@ fn serialize_trie_node_pointer(b: &mut Bencher) { b.iter(|| ToBytes::to_bytes(black_box(&node))); } -#[bench] fn deserialize_trie_node_pointer(b: &mut Bencher) { let node = Trie::::Extension { affix: (0..255).collect(), @@ -69,3 +59,18 @@ fn deserialize_trie_node_pointer(b: &mut Bencher) { b.iter(|| Trie::::from_bytes(black_box(&node_bytes))); } + +fn trie_bench(c: &mut Criterion) { + c.bench_function("serialize_trie_leaf", serialize_trie_leaf); + c.bench_function("deserialize_trie_leaf", deserialize_trie_leaf); + c.bench_function("serialize_trie_node", serialize_trie_node); + c.bench_function("deserialize_trie_node", deserialize_trie_node); + c.bench_function("serialize_trie_node_pointer", serialize_trie_node_pointer); + c.bench_function( + "deserialize_trie_node_pointer", + deserialize_trie_node_pointer, + ); +} + +criterion_group!(benches, trie_bench); +criterion_main!(benches); diff --git a/types/Cargo.toml b/types/Cargo.toml index 7b98a0c8a4..9e62e8ad40 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -32,6 +32,12 @@ uint = { version = "0.8.2", default-features = false, features = [] } [dev-dependencies] proptest = "0.9.4" version-sync = "0.8" +criterion = "0.3.3" + +[[bench]] +name = "bytesrepr_bench" +harness = false [package.metadata.docs.rs] features = ["no-unstable-features"] + diff --git a/types/benches/bytesrepr_bench.rs b/types/benches/bytesrepr_bench.rs index 182dd24c64..6d8348057f 100644 --- a/types/benches/bytesrepr_bench.rs +++ b/types/benches/bytesrepr_bench.rs @@ -1,11 +1,7 @@ -#![feature(test)] - -extern crate test; +use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion}; use std::{collections::BTreeMap, iter}; -use test::{black_box, Bencher}; - use casperlabs_types::{ account::AccountHash, bytesrepr::{self, FromBytes, ToBytes}, @@ -26,13 +22,11 @@ fn prepare_vector(size: usize) -> Vec { (0..size as i32).collect() } -#[bench] fn serialize_vector_of_i32s(b: &mut Bencher) { let data = prepare_vector(black_box(BATCH)); b.iter(|| data.to_bytes()); } -#[bench] fn deserialize_vector_of_i32s(b: &mut Bencher) { let data = prepare_vector(black_box(BATCH)).to_bytes().unwrap(); b.iter(|| { @@ -41,7 +35,6 @@ fn deserialize_vector_of_i32s(b: &mut Bencher) { }); } -#[bench] fn serialize_vector_of_u8(b: &mut Bencher) { // 0, 1, ... 254, 255, 0, 1, ... let data: Vec = prepare_vector(BATCH) @@ -51,7 +44,6 @@ fn serialize_vector_of_u8(b: &mut Bencher) { b.iter(|| data.to_bytes()); } -#[bench] fn deserialize_vector_of_u8(b: &mut Bencher) { // 0, 1, ... 254, 255, 0, 1, ... let data: Vec = prepare_vector(BATCH) @@ -63,44 +55,36 @@ fn deserialize_vector_of_u8(b: &mut Bencher) { b.iter(|| Vec::::from_bytes(&data)) } -#[bench] fn serialize_u8(b: &mut Bencher) { b.iter(|| ToBytes::to_bytes(black_box(&129u8))); } -#[bench] fn deserialize_u8(b: &mut Bencher) { b.iter(|| u8::from_bytes(black_box(&[129u8]))); } -#[bench] fn serialize_i32(b: &mut Bencher) { b.iter(|| ToBytes::to_bytes(black_box(&1_816_142_132i32))); } -#[bench] fn deserialize_i32(b: &mut Bencher) { b.iter(|| i32::from_bytes(black_box(&[0x34, 0x21, 0x40, 0x6c]))); } -#[bench] fn serialize_u64(b: &mut Bencher) { b.iter(|| ToBytes::to_bytes(black_box(&14_157_907_845_468_752_670u64))); } -#[bench] fn deserialize_u64(b: &mut Bencher) { b.iter(|| u64::from_bytes(black_box(&[0x1e, 0x8b, 0xe1, 0x73, 0x2c, 0xfe, 0x7a, 0xc4]))); } -#[bench] fn serialize_some_u64(b: &mut Bencher) { let data = Some(14_157_907_845_468_752_670u64); b.iter(|| ToBytes::to_bytes(black_box(&data))); } -#[bench] fn deserialize_some_u64(b: &mut Bencher) { let data = Some(14_157_907_845_468_752_670u64); let data = data.to_bytes().unwrap(); @@ -108,21 +92,18 @@ fn deserialize_some_u64(b: &mut Bencher) { b.iter(|| Option::::from_bytes(&data)); } -#[bench] fn serialize_none_u64(b: &mut Bencher) { let data: Option = None; b.iter(|| ToBytes::to_bytes(black_box(&data))); } -#[bench] fn deserialize_ok_u64(b: &mut Bencher) { let data: Option = None; let data = data.to_bytes().unwrap(); b.iter(|| Option::::from_bytes(&data)); } -#[bench] fn serialize_vector_of_vector_of_u8(b: &mut Bencher) { let data: Vec> = (0..4) .map(|_v| { @@ -138,7 +119,6 @@ fn serialize_vector_of_vector_of_u8(b: &mut Bencher) { b.iter(|| data.to_bytes()); } -#[bench] fn deserialize_vector_of_vector_of_u8(b: &mut Bencher) { let data: Vec = (0..4) .map(|_v| { @@ -155,7 +135,6 @@ fn deserialize_vector_of_vector_of_u8(b: &mut Bencher) { b.iter(|| Vec::>::from_bytes(&data)); } -#[bench] fn serialize_tree_map(b: &mut Bencher) { let data = { let mut res = BTreeMap::new(); @@ -168,7 +147,6 @@ fn serialize_tree_map(b: &mut Bencher) { b.iter(|| ToBytes::to_bytes(black_box(&data))); } -#[bench] fn deserialize_treemap(b: &mut Bencher) { let data = { let mut res = BTreeMap::new(); @@ -181,21 +159,18 @@ fn deserialize_treemap(b: &mut Bencher) { b.iter(|| BTreeMap::::from_bytes(black_box(&data))); } -#[bench] fn serialize_string(b: &mut Bencher) { let lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; let data = lorem.to_string(); b.iter(|| ToBytes::to_bytes(black_box(&data))); } -#[bench] fn deserialize_string(b: &mut Bencher) { let lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; let data = lorem.to_bytes().unwrap(); b.iter(|| String::from_bytes(&data)); } -#[bench] fn serialize_vec_of_string(b: &mut Bencher) { let lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.".to_string(); let array_of_lorem: Vec = lorem.split(' ').map(Into::into).collect(); @@ -203,7 +178,6 @@ fn serialize_vec_of_string(b: &mut Bencher) { b.iter(|| ToBytes::to_bytes(black_box(&data))); } -#[bench] fn deserialize_vec_of_string(b: &mut Bencher) { let lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.".to_string(); let array_of_lorem: Vec = lorem.split(' ').map(Into::into).collect(); @@ -212,26 +186,22 @@ fn deserialize_vec_of_string(b: &mut Bencher) { b.iter(|| Vec::::from_bytes(&data)); } -#[bench] fn serialize_unit(b: &mut Bencher) { b.iter(|| ToBytes::to_bytes(black_box(&()))) } -#[bench] fn deserialize_unit(b: &mut Bencher) { let data = ().to_bytes().unwrap(); b.iter(|| <()>::from_bytes(&data)) } -#[bench] fn serialize_key_account(b: &mut Bencher) { let account = Key::Account(AccountHash::new([0u8; 32])); b.iter(|| ToBytes::to_bytes(black_box(&account))) } -#[bench] fn deserialize_key_account(b: &mut Bencher) { let account = Key::Account(AccountHash::new([0u8; 32])); let account_bytes = account.to_bytes().unwrap(); @@ -239,13 +209,11 @@ fn deserialize_key_account(b: &mut Bencher) { b.iter(|| Key::from_bytes(black_box(&account_bytes))) } -#[bench] fn serialize_key_hash(b: &mut Bencher) { let hash = Key::Hash([0u8; 32]); b.iter(|| ToBytes::to_bytes(black_box(&hash))) } -#[bench] fn deserialize_key_hash(b: &mut Bencher) { let hash = Key::Hash([0u8; 32]); let hash_bytes = hash.to_bytes().unwrap(); @@ -253,13 +221,11 @@ fn deserialize_key_hash(b: &mut Bencher) { b.iter(|| Key::from_bytes(black_box(&hash_bytes))) } -#[bench] fn serialize_key_uref(b: &mut Bencher) { let uref = Key::URef(URef::new([0u8; 32], AccessRights::ADD_WRITE)); b.iter(|| ToBytes::to_bytes(black_box(&uref))) } -#[bench] fn deserialize_key_uref(b: &mut Bencher) { let uref = Key::URef(URef::new([0u8; 32], AccessRights::ADD_WRITE)); let uref_bytes = uref.to_bytes().unwrap(); @@ -267,7 +233,6 @@ fn deserialize_key_uref(b: &mut Bencher) { b.iter(|| Key::from_bytes(black_box(&uref_bytes))) } -#[bench] fn serialize_vec_of_keys(b: &mut Bencher) { let keys: Vec = (0..32) .map(|i| Key::URef(URef::new([i; 32], AccessRights::ADD_WRITE))) @@ -275,7 +240,6 @@ fn serialize_vec_of_keys(b: &mut Bencher) { b.iter(|| ToBytes::to_bytes(black_box(&keys))) } -#[bench] fn deserialize_vec_of_keys(b: &mut Bencher) { let keys: Vec = (0..32) .map(|i| Key::URef(URef::new([i; 32], AccessRights::ADD_WRITE))) @@ -284,67 +248,55 @@ fn deserialize_vec_of_keys(b: &mut Bencher) { b.iter(|| Vec::::from_bytes(black_box(&keys_bytes))); } -#[bench] fn serialize_access_rights_read(b: &mut Bencher) { b.iter(|| AccessRights::READ.to_bytes()); } -#[bench] fn deserialize_access_rights_read(b: &mut Bencher) { let data = AccessRights::READ.to_bytes().unwrap(); b.iter(|| AccessRights::from_bytes(&data)); } -#[bench] fn serialize_access_rights_write(b: &mut Bencher) { b.iter(|| AccessRights::WRITE.to_bytes()); } -#[bench] fn deserialize_access_rights_write(b: &mut Bencher) { let data = AccessRights::WRITE.to_bytes().unwrap(); b.iter(|| AccessRights::from_bytes(&data)); } -#[bench] fn serialize_access_rights_add(b: &mut Bencher) { b.iter(|| AccessRights::ADD.to_bytes()); } -#[bench] fn deserialize_access_rights_add(b: &mut Bencher) { let data = AccessRights::ADD.to_bytes().unwrap(); b.iter(|| AccessRights::from_bytes(&data)); } -#[bench] fn serialize_access_rights_read_add(b: &mut Bencher) { b.iter(|| AccessRights::READ_ADD.to_bytes()); } -#[bench] fn deserialize_access_rights_read_add(b: &mut Bencher) { let data = AccessRights::READ_ADD.to_bytes().unwrap(); b.iter(|| AccessRights::from_bytes(&data)); } -#[bench] fn serialize_access_rights_read_write(b: &mut Bencher) { b.iter(|| AccessRights::READ_WRITE.to_bytes()); } -#[bench] fn deserialize_access_rights_read_write(b: &mut Bencher) { let data = AccessRights::READ_WRITE.to_bytes().unwrap(); b.iter(|| AccessRights::from_bytes(&data)); } -#[bench] fn serialize_access_rights_add_write(b: &mut Bencher) { b.iter(|| AccessRights::ADD_WRITE.to_bytes()); } -#[bench] fn deserialize_access_rights_add_write(b: &mut Bencher) { let data = AccessRights::ADD_WRITE.to_bytes().unwrap(); b.iter(|| AccessRights::from_bytes(&data)); @@ -365,87 +317,70 @@ fn benchmark_deserialization(b: &mut Bencher, }); } -#[bench] fn serialize_cl_value_int32(b: &mut Bencher) { b.iter(|| serialize_cl_value(TEST_I32)); } -#[bench] fn deserialize_cl_value_int32(b: &mut Bencher) { benchmark_deserialization(b, TEST_I32); } -#[bench] fn serialize_cl_value_uint128(b: &mut Bencher) { b.iter(|| serialize_cl_value(TEST_U128)); } -#[bench] fn deserialize_cl_value_uint128(b: &mut Bencher) { benchmark_deserialization(b, TEST_U128); } -#[bench] fn serialize_cl_value_uint256(b: &mut Bencher) { b.iter(|| serialize_cl_value(TEST_U256)); } -#[bench] fn deserialize_cl_value_uint256(b: &mut Bencher) { benchmark_deserialization(b, TEST_U256); } -#[bench] fn serialize_cl_value_uint512(b: &mut Bencher) { b.iter(|| serialize_cl_value(TEST_U512)); } -#[bench] fn deserialize_cl_value_uint512(b: &mut Bencher) { benchmark_deserialization(b, TEST_U512); } -#[bench] fn serialize_cl_value_bytearray(b: &mut Bencher) { b.iter(|| serialize_cl_value((0..255).collect::>())); } -#[bench] fn deserialize_cl_value_bytearray(b: &mut Bencher) { benchmark_deserialization(b, (0..255).collect::>()); } -#[bench] fn serialize_cl_value_listint32(b: &mut Bencher) { b.iter(|| serialize_cl_value((0..1024).collect::>())); } -#[bench] fn deserialize_cl_value_listint32(b: &mut Bencher) { benchmark_deserialization(b, (0..1024).collect::>()); } -#[bench] fn serialize_cl_value_string(b: &mut Bencher) { b.iter(|| serialize_cl_value(TEST_STR_1.to_string())); } -#[bench] fn deserialize_cl_value_string(b: &mut Bencher) { benchmark_deserialization(b, TEST_STR_1.to_string()); } -#[bench] fn serialize_cl_value_liststring(b: &mut Bencher) { b.iter(|| serialize_cl_value(vec![TEST_STR_1.to_string(), TEST_STR_2.to_string()])); } -#[bench] fn deserialize_cl_value_liststring(b: &mut Bencher) { benchmark_deserialization(b, vec![TEST_STR_1.to_string(), TEST_STR_2.to_string()]); } -#[bench] fn serialize_cl_value_namedkey(b: &mut Bencher) { b.iter(|| { serialize_cl_value(( @@ -455,7 +390,6 @@ fn serialize_cl_value_namedkey(b: &mut Bencher) { }); } -#[bench] fn deserialize_cl_value_namedkey(b: &mut Bencher) { benchmark_deserialization( b, @@ -466,13 +400,11 @@ fn deserialize_cl_value_namedkey(b: &mut Bencher) { ); } -#[bench] fn serialize_u128(b: &mut Bencher) { let num_u128 = U128::default(); b.iter(|| ToBytes::to_bytes(black_box(&num_u128))) } -#[bench] fn deserialize_u128(b: &mut Bencher) { let num_u128 = U128::default(); let num_u128_bytes = num_u128.to_bytes().unwrap(); @@ -480,13 +412,11 @@ fn deserialize_u128(b: &mut Bencher) { b.iter(|| U128::from_bytes(black_box(&num_u128_bytes))) } -#[bench] fn serialize_u256(b: &mut Bencher) { let num_u256 = U256::default(); b.iter(|| ToBytes::to_bytes(black_box(&num_u256))) } -#[bench] fn deserialize_u256(b: &mut Bencher) { let num_u256 = U256::default(); let num_u256_bytes = num_u256.to_bytes().unwrap(); @@ -494,16 +424,139 @@ fn deserialize_u256(b: &mut Bencher) { b.iter(|| U256::from_bytes(black_box(&num_u256_bytes))) } -#[bench] fn serialize_u512(b: &mut Bencher) { let num_u512 = U512::default(); b.iter(|| ToBytes::to_bytes(black_box(&num_u512))) } -#[bench] fn deserialize_u512(b: &mut Bencher) { let num_u512 = U512::default(); let num_u512_bytes = num_u512.to_bytes().unwrap(); b.iter(|| U512::from_bytes(black_box(&num_u512_bytes))) } + +fn bytesrepr_bench(c: &mut Criterion) { + c.bench_function("serialize_vector_of_i32s", serialize_vector_of_i32s); + c.bench_function("deserialize_vector_of_i32s", deserialize_vector_of_i32s); + c.bench_function("serialize_vector_of_u8", serialize_vector_of_u8); + c.bench_function("deserialize_vector_of_u8", deserialize_vector_of_u8); + c.bench_function("serialize_u8", serialize_u8); + c.bench_function("deserialize_u8", deserialize_u8); + c.bench_function("serialize_i32", serialize_i32); + c.bench_function("deserialize_i32", deserialize_i32); + c.bench_function("serialize_u64", serialize_u64); + c.bench_function("deserialize_u64", deserialize_u64); + c.bench_function("serialize_some_u64", serialize_some_u64); + c.bench_function("deserialize_some_u64", deserialize_some_u64); + c.bench_function("serialize_none_u64", serialize_none_u64); + c.bench_function("deserialize_ok_u64", deserialize_ok_u64); + c.bench_function( + "serialize_vector_of_vector_of_u8", + serialize_vector_of_vector_of_u8, + ); + c.bench_function( + "deserialize_vector_of_vector_of_u8", + deserialize_vector_of_vector_of_u8, + ); + c.bench_function("serialize_tree_map", serialize_tree_map); + c.bench_function("deserialize_treemap", deserialize_treemap); + c.bench_function("serialize_string", serialize_string); + c.bench_function("deserialize_string", deserialize_string); + c.bench_function("serialize_vec_of_string", serialize_vec_of_string); + c.bench_function("deserialize_vec_of_string", deserialize_vec_of_string); + c.bench_function("serialize_unit", serialize_unit); + c.bench_function("deserialize_unit", deserialize_unit); + c.bench_function("serialize_key_account", serialize_key_account); + c.bench_function("deserialize_key_account", deserialize_key_account); + c.bench_function("serialize_key_hash", serialize_key_hash); + c.bench_function("deserialize_key_hash", deserialize_key_hash); + c.bench_function("serialize_key_uref", serialize_key_uref); + c.bench_function("deserialize_key_uref", deserialize_key_uref); + c.bench_function("serialize_vec_of_keys", serialize_vec_of_keys); + c.bench_function("deserialize_vec_of_keys", deserialize_vec_of_keys); + c.bench_function("serialize_access_rights_read", serialize_access_rights_read); + c.bench_function( + "deserialize_access_rights_read", + deserialize_access_rights_read, + ); + c.bench_function( + "serialize_access_rights_write", + serialize_access_rights_write, + ); + c.bench_function( + "deserialize_access_rights_write", + deserialize_access_rights_write, + ); + c.bench_function("serialize_access_rights_add", serialize_access_rights_add); + c.bench_function( + "deserialize_access_rights_add", + deserialize_access_rights_add, + ); + c.bench_function( + "serialize_access_rights_read_add", + serialize_access_rights_read_add, + ); + c.bench_function( + "deserialize_access_rights_read_add", + deserialize_access_rights_read_add, + ); + c.bench_function( + "serialize_access_rights_read_write", + serialize_access_rights_read_write, + ); + c.bench_function( + "deserialize_access_rights_read_write", + deserialize_access_rights_read_write, + ); + c.bench_function( + "serialize_access_rights_add_write", + serialize_access_rights_add_write, + ); + c.bench_function( + "deserialize_access_rights_add_write", + deserialize_access_rights_add_write, + ); + c.bench_function("serialize_cl_value_int32", serialize_cl_value_int32); + c.bench_function("deserialize_cl_value_int32", deserialize_cl_value_int32); + c.bench_function("serialize_cl_value_uint128", serialize_cl_value_uint128); + c.bench_function("deserialize_cl_value_uint128", deserialize_cl_value_uint128); + c.bench_function("serialize_cl_value_uint256", serialize_cl_value_uint256); + c.bench_function("deserialize_cl_value_uint256", deserialize_cl_value_uint256); + c.bench_function("serialize_cl_value_uint512", serialize_cl_value_uint512); + c.bench_function("deserialize_cl_value_uint512", deserialize_cl_value_uint512); + c.bench_function("serialize_cl_value_bytearray", serialize_cl_value_bytearray); + c.bench_function( + "deserialize_cl_value_bytearray", + deserialize_cl_value_bytearray, + ); + c.bench_function("serialize_cl_value_listint32", serialize_cl_value_listint32); + c.bench_function( + "deserialize_cl_value_listint32", + deserialize_cl_value_listint32, + ); + c.bench_function("serialize_cl_value_string", serialize_cl_value_string); + c.bench_function("deserialize_cl_value_string", deserialize_cl_value_string); + c.bench_function( + "serialize_cl_value_liststring", + serialize_cl_value_liststring, + ); + c.bench_function( + "deserialize_cl_value_liststring", + deserialize_cl_value_liststring, + ); + c.bench_function("serialize_cl_value_namedkey", serialize_cl_value_namedkey); + c.bench_function( + "deserialize_cl_value_namedkey", + deserialize_cl_value_namedkey, + ); + c.bench_function("serialize_u128", serialize_u128); + c.bench_function("deserialize_u128", deserialize_u128); + c.bench_function("serialize_u256", serialize_u256); + c.bench_function("deserialize_u256", deserialize_u256); + c.bench_function("serialize_u512", serialize_u512); + c.bench_function("deserialize_u512", deserialize_u512); +} + +criterion_group!(benches, bytesrepr_bench); +criterion_main!(benches); diff --git a/types/src/uint.rs b/types/src/uint.rs index 77708af26b..ea3238ec89 100644 --- a/types/src/uint.rs +++ b/types/src/uint.rs @@ -9,8 +9,7 @@ use crate::bytesrepr::{self, Error, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}; clippy::assign_op_pattern, clippy::ptr_offset_with_cast, clippy::range_plus_one, - clippy::transmute_ptr_to_ptr, - clippy::reversed_empty_ranges + clippy::transmute_ptr_to_ptr )] mod macro_code { use uint::construct_uint; From 5d5493250e2cdc3d3ccf425c9c53876171852607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Papierski?= Date: Tue, 7 Jul 2020 12:27:52 +0200 Subject: [PATCH 6/8] NDRS-90: Temporarily pin to nightly. Bringing stable support would be part of separate story. Main blocker is the performance penalty we'd have to pay removing specialization of ToBytes for Vec. --- Makefile | 4 ++-- grpc/server/src/engine_server/mappings/ipc/deploy_result.rs | 1 + node/src/components/consensus/highway_core/state.rs | 1 + rust-toolchain | 1 + types/src/uint.rs | 3 ++- 5 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 rust-toolchain diff --git a/Makefile b/Makefile index 6e7a7114e2..637bce4b00 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CARGO = $(or $(shell which cargo), $(HOME)/.cargo/bin/cargo) RUSTUP = $(or $(shell which rustup), $(HOME)/.cargo/bin/rustup) NPM = $(or $(shell which npm), /usr/bin/npm) -RUST_TOOLCHAIN := nightly +RUST_TOOLCHAIN := $(shell cat rust-toolchain) CARGO := $(CARGO) $(CARGO_OPTS) @@ -250,7 +250,7 @@ setup-audit: $(CARGO) install cargo-audit .PHONY: setup-rs -setup-rs: +setup-rs: rust-toolchain $(RUSTUP) update --no-self-update $(RUSTUP) toolchain install --no-self-update $(RUST_TOOLCHAIN) $(RUSTUP) target add --toolchain $(RUST_TOOLCHAIN) wasm32-unknown-unknown diff --git a/grpc/server/src/engine_server/mappings/ipc/deploy_result.rs b/grpc/server/src/engine_server/mappings/ipc/deploy_result.rs index 0b3df5b009..b7262f055d 100644 --- a/grpc/server/src/engine_server/mappings/ipc/deploy_result.rs +++ b/grpc/server/src/engine_server/mappings/ipc/deploy_result.rs @@ -22,6 +22,7 @@ impl From for DeployResult { } } +#[allow(clippy::unnested_or_patterns)] impl From<(EngineStateError, ExecutionEffect, Gas)> for DeployResult { fn from((engine_state_error, effect, cost): (EngineStateError, ExecutionEffect, Gas)) -> Self { match engine_state_error { diff --git a/node/src/components/consensus/highway_core/state.rs b/node/src/components/consensus/highway_core/state.rs index 91ffa7ab44..d46bc6d0ba 100644 --- a/node/src/components/consensus/highway_core/state.rs +++ b/node/src/components/consensus/highway_core/state.rs @@ -305,6 +305,7 @@ impl State { /// Returns the panorama seeing all votes seen by `pan` with a timestamp no later than /// `instant`. Accusations are preserved regardless of the evidence's timestamp. + #[allow(clippy::unnested_or_patterns)] pub(crate) fn panorama_cutoff(&self, pan: &Panorama, instant: u64) -> Panorama { let obs_cutoff = |obs: &Observation| match obs { Observation::Correct(vhash) => self diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000000..67e5724258 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly-2020-07-05 diff --git a/types/src/uint.rs b/types/src/uint.rs index ea3238ec89..6cd991df04 100644 --- a/types/src/uint.rs +++ b/types/src/uint.rs @@ -9,7 +9,8 @@ use crate::bytesrepr::{self, Error, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}; clippy::assign_op_pattern, clippy::ptr_offset_with_cast, clippy::range_plus_one, - clippy::transmute_ptr_to_ptr + clippy::transmute_ptr_to_ptr, + clippy::clippy::reversed_empty_ranges )] mod macro_code { use uint::construct_uint; From 2115b58c96773d6c4b53611d8ea771f83ca21a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Papierski?= Date: Tue, 7 Jul 2020 12:28:26 +0200 Subject: [PATCH 7/8] NDRS-90: Move contract related mods to component. --- .gitignore | 2 +- Cargo.lock | 13 ++++++- Cargo.toml | 12 ++++--- Makefile | 34 +++++++++---------- client/Cargo.toml | 15 ++++++++ {node/src/apps/client => client/src}/main.rs | 0 .../engine_server/mappings/ipc/deploy_item.rs | 2 +- .../mappings/ipc/deploy_result.rs | 8 +++-- .../engine_server/mappings/ipc/exec_config.rs | 2 +- .../mappings/ipc/executable_deploy_item.rs | 2 +- .../mappings/ipc/execute_request.rs | 4 +-- .../mappings/ipc/execution_effect.rs | 4 ++- .../mappings/ipc/genesis_account.rs | 4 +-- .../mappings/ipc/genesis_config.rs | 2 +- .../mappings/ipc/query_request.rs | 4 +-- .../mappings/ipc/run_genesis_request.rs | 2 +- .../mappings/ipc/upgrade_request.rs | 2 +- .../engine_server/mappings/ipc/wasm_costs.rs | 4 +-- grpc/server/src/engine_server/mappings/mod.rs | 2 +- .../engine_server/mappings/state/account.rs | 6 ++-- .../mappings/state/stored_value.rs | 4 +-- .../mappings/transforms/error.rs | 2 +- .../mappings/transforms/transform.rs | 4 +-- .../mappings/transforms/transform_entry.rs | 4 +-- .../mappings/transforms/transform_map.rs | 4 +-- grpc/server/src/engine_server/mod.rs | 6 ++-- grpc/server/src/main.rs | 8 ++--- .../{test-support => test_support}/Cargo.toml | 2 +- grpc/{test-support => test_support}/README.md | 0 .../src/account.rs | 12 +++---- .../src/code.rs | 0 .../src/error.rs | 2 +- .../src/internal/additive_map_diff.rs | 2 +- .../src/internal/deploy_item_builder.rs | 2 +- .../src/internal/exec_with_return.rs | 10 +++--- .../src/internal/execute_request_builder.rs | 4 ++- .../src/internal/mod.rs | 6 ++-- .../src/internal/upgrade_request_builder.rs | 2 +- .../src/internal/utils.rs | 6 ++-- .../src/internal/wasm_test_builder.rs | 6 ++-- .../{test-support => test_support}/src/lib.rs | 0 .../src/session.rs | 2 +- .../src/test_context.rs | 4 +-- .../src/value.rs | 2 +- .../tests/version_numbers.rs | 0 grpc/tests/Cargo.toml | 4 +-- grpc/tests/benches/transfer_bench.rs | 2 +- grpc/tests/src/logging/metrics.rs | 2 +- .../src/profiling/host_function_metrics.rs | 4 +-- grpc/tests/src/profiling/simple_transfer.rs | 2 +- grpc/tests/src/profiling/state_initializer.rs | 2 +- .../contract_api/account/authorized_keys.rs | 2 +- .../src/test/contract_api/create_purse.rs | 2 +- .../tests/src/test/contract_api/main_purse.rs | 2 +- grpc/tests/src/test/contract_api/subcall.rs | 2 +- grpc/tests/src/test/contract_api/transfer.rs | 4 +-- .../contract_api/transfer_purse_to_account.rs | 2 +- .../src/test/contract_api/transfer_stored.rs | 4 +-- .../test/contract_api/transfer_u512_stored.rs | 4 +-- grpc/tests/src/test/contract_context.rs | 2 +- .../src/test/deploy/non_standard_payment.rs | 4 +-- grpc/tests/src/test/deploy/preconditions.rs | 2 +- .../tests/src/test/deploy/stored_contracts.rs | 6 ++-- grpc/tests/src/test/groups.rs | 2 +- grpc/tests/src/test/manage_groups.rs | 2 +- grpc/tests/src/test/regression/ee_460.rs | 2 +- grpc/tests/src/test/regression/ee_470.rs | 2 +- grpc/tests/src/test/regression/ee_532.rs | 2 +- grpc/tests/src/test/regression/ee_572.rs | 2 +- grpc/tests/src/test/regression/ee_584.rs | 2 +- grpc/tests/src/test/regression/ee_598.rs | 4 +-- grpc/tests/src/test/regression/ee_599.rs | 4 +-- grpc/tests/src/test/regression/ee_601.rs | 2 +- grpc/tests/src/test/regression/ee_803.rs | 4 +-- .../src/test/system_contracts/genesis.rs | 4 +-- .../src/test/system_contracts/mint_install.rs | 4 +-- .../src/test/system_contracts/pos_install.rs | 2 +- .../proof_of_stake/bonding.rs | 4 +-- .../proof_of_stake/commit_validators.rs | 4 +-- .../proof_of_stake/finalize_payment.rs | 4 +-- .../test/system_contracts/standard_payment.rs | 4 +-- .../standard_payment_install.rs | 4 +-- .../src/test/system_contracts/upgrade.rs | 6 ++-- grpc/tests/src/test/upgrade.rs | 2 +- grpc/tests/src/test/wasmless_transfer.rs | 4 ++- node/Cargo.toml | 10 +----- node/benches/trie_bench.rs | 6 ++-- node/src/{apps/node => app}/cli.rs | 0 node/src/{apps/node => app}/config.rs | 0 node/src/{apps/node => app}/logging.rs | 0 node/src/{apps/node => app}/main.rs | 0 node/src/components/contract_runtime.rs | 9 +++-- .../src/components/contract_runtime/config.rs | 2 +- .../contract_runtime/core.rs} | 0 .../core}/engine_state/deploy_item.rs | 2 +- .../core}/engine_state/engine_config.rs | 0 .../core}/engine_state/error.rs | 14 ++++---- .../engine_state/executable_deploy_item.rs | 4 +-- .../core}/engine_state/execute_request.rs | 2 +- .../core}/engine_state/execution_effect.rs | 34 ++++++++++--------- .../core}/engine_state/execution_result.rs | 4 +-- .../core}/engine_state/genesis.rs | 10 +++--- .../core}/engine_state/mod.rs | 10 +++--- .../contract_runtime/core}/engine_state/op.rs | 0 .../core}/engine_state/query.rs | 6 ++-- .../core}/engine_state/run_genesis_request.rs | 2 +- .../engine_state/system_contract_cache.rs | 2 +- .../core}/engine_state/transfer.rs | 13 +++---- .../core}/engine_state/upgrade.rs | 8 ++--- .../core}/engine_state/utils.rs | 0 .../core}/execution/address_generator.rs | 2 +- .../contract_runtime/core}/execution/error.rs | 12 +++---- .../core}/execution/executor.rs | 8 +++-- .../contract_runtime/core}/execution/mod.rs | 0 .../contract_runtime/core}/execution/tests.rs | 4 +-- .../contract_runtime/core}/resolvers/error.rs | 0 .../core}/resolvers/memory_resolver.rs | 0 .../contract_runtime/core}/resolvers/mod.rs | 2 +- .../core}/resolvers/v1_function_index.rs | 0 .../core}/resolvers/v1_resolver.rs | 0 .../contract_runtime/core}/runtime/args.rs | 0 .../core}/runtime/externals.rs | 6 ++-- .../core}/runtime/mint_internal.rs | 6 ++-- .../contract_runtime/core}/runtime/mod.rs | 10 ++++-- .../core}/runtime/proof_of_stake_internal.rs | 6 ++-- .../core}/runtime/scoped_instrumenter.rs | 4 +-- .../runtime/standard_payment_internal.rs | 6 ++-- .../core}/runtime_context/mod.rs | 8 +++-- .../core}/runtime_context/tests.rs | 6 ++-- .../core}/tracking_copy/byte_size.rs | 2 +- .../core}/tracking_copy/ext.rs | 8 ++--- .../core}/tracking_copy/meter.rs | 2 +- .../core}/tracking_copy/mod.rs | 8 +++-- .../core}/tracking_copy/tests.rs | 6 ++-- .../contract_runtime/shared.rs} | 0 .../contract_runtime/shared}/account.rs | 2 +- .../shared}/account/action_thresholds.rs | 0 .../shared}/account/associated_keys.rs | 0 .../contract_runtime/shared}/additive_map.rs | 2 +- .../contract_runtime/shared}/gas.rs | 4 +-- .../shared}/logging/README.md | 0 .../contract_runtime/shared}/logging/mod.rs | 2 +- .../shared}/logging/settings.rs | 0 .../shared}/logging/structured_message.rs | 4 +-- .../shared}/logging/terminal_logger.rs | 2 +- .../contract_runtime/shared}/motes.rs | 4 +-- .../shared}/newtypes/macros.rs | 0 .../contract_runtime/shared}/newtypes/mod.rs | 2 +- .../contract_runtime/shared}/page_size.rs | 0 .../contract_runtime/shared}/socket.rs | 0 .../contract_runtime/shared}/stored_value.rs | 4 +-- .../contract_runtime/shared}/test_utils.rs | 4 +-- .../contract_runtime/shared}/transform.rs | 8 +++-- .../contract_runtime/shared}/type_mismatch.rs | 0 .../contract_runtime/shared}/utils.rs | 0 .../contract_runtime/shared}/wasm.rs | 2 +- .../contract_runtime/shared}/wasm_costs.rs | 4 +-- .../contract_runtime/shared}/wasm_prep.rs | 2 +- .../contract_runtime/storage.rs} | 2 +- .../storage}/error/in_memory.rs | 0 .../contract_runtime/storage}/error/lmdb.rs | 0 .../contract_runtime/storage}/error/mod.rs | 0 .../storage}/global_state/in_memory.rs | 4 +-- .../storage}/global_state/lmdb.rs | 6 ++-- .../storage}/global_state/mod.rs | 4 +-- .../storage}/protocol_data.rs | 8 +++-- .../storage}/protocol_data_store/in_memory.rs | 2 +- .../storage}/protocol_data_store/lmdb.rs | 2 +- .../storage}/protocol_data_store/mod.rs | 2 +- .../storage}/protocol_data_store/tests/mod.rs | 0 .../protocol_data_store/tests/proptests.rs | 2 +- .../contract_runtime/storage}/store/mod.rs | 2 +- .../storage}/store/store_ext.rs | 2 +- .../contract_runtime/storage}/store/tests.rs | 2 +- .../storage}/transaction_source/in_memory.rs | 2 +- .../storage}/transaction_source/lmdb.rs | 2 +- .../storage}/transaction_source/mod.rs | 0 .../contract_runtime/storage}/trie/gens.rs | 2 +- .../contract_runtime/storage}/trie/mod.rs | 6 ++-- .../contract_runtime/storage}/trie/tests.rs | 6 ++-- .../storage}/trie_store/in_memory.rs | 14 ++++---- .../storage}/trie_store/lmdb.rs | 16 ++++----- .../storage}/trie_store/mod.rs | 4 +-- .../storage}/trie_store/operations/mod.rs | 4 +-- .../trie_store/operations/tests/ee_699.rs | 2 +- .../trie_store/operations/tests/keys.rs | 16 ++++----- .../trie_store/operations/tests/mod.rs | 4 +-- .../trie_store/operations/tests/proptests.rs | 0 .../trie_store/operations/tests/read.rs | 2 +- .../trie_store/operations/tests/scan.rs | 4 +-- .../trie_store/operations/tests/write.rs | 0 .../storage}/trie_store/tests/concurrent.rs | 2 +- .../storage}/trie_store/tests/mod.rs | 4 +-- .../storage}/trie_store/tests/proptests.rs | 10 +++--- .../storage}/trie_store/tests/simple.rs | 2 +- node/src/components/storage/config.rs | 2 +- node/src/lib.rs | 3 -- .../contract/Cargo.toml | 0 .../contract/README.md | 0 .../contract/src/contract_api/account.rs | 0 .../contract/src/contract_api/mod.rs | 0 .../contract/src/contract_api/runtime.rs | 0 .../contract/src/contract_api/storage.rs | 0 .../contract/src/contract_api/system.rs | 0 .../contract/src/ext_ffi.rs | 0 .../contract/src/handlers.rs | 0 .../contract/src/lib.rs | 0 .../contract/src/unwrap_or_revert.rs | 0 .../contract/tests/version_numbers.rs | 0 .../contract_as}/.gitignore | 0 .../contract_as}/.npmignore | 0 .../contract_as}/.npmrc | 0 .../contract_as}/README.md | 2 +- .../contract_as}/assembly/account.ts | 0 .../contract_as}/assembly/bignum.ts | 0 .../contract_as}/assembly/bytesrepr.ts | 0 .../contract_as}/assembly/clvalue.ts | 0 .../contract_as}/assembly/constants.ts | 0 .../contract_as}/assembly/contracts.ts | 0 .../contract_as}/assembly/error.ts | 0 .../contract_as}/assembly/externals.ts | 0 .../contract_as}/assembly/index.ts | 0 .../contract_as}/assembly/key.ts | 0 .../contract_as}/assembly/local.ts | 0 .../contract_as}/assembly/option.ts | 0 .../contract_as}/assembly/pair.ts | 0 .../contract_as}/assembly/purse.ts | 0 .../contract_as}/assembly/runtime_args.ts | 0 .../contract_as}/assembly/tsconfig.json | 0 .../contract_as}/assembly/unit.ts | 0 .../contract_as}/assembly/uref.ts | 0 .../contract_as}/assembly/utils.ts | 0 .../contract_as}/build/.gitignore | 0 .../contract_as}/index.js | 0 .../contract_as}/package-lock.json | 0 .../contract_as}/package.json | 0 .../tests/assembly/bignum.spec.as.ts | 0 .../tests/assembly/bytesrepr.spec.as.ts | 0 .../tests/assembly/runtime_args.spec.as.ts | 0 .../tests/assembly/utils.spec.as.ts | 0 .../contract_as}/tests/bignum.spec.ts | 0 .../contract_as}/tests/bytesrepr.spec.ts | 0 .../contract_as}/tests/runtime_args.spec.ts | 0 .../contract_as}/tests/tsconfig.json | 0 .../contract_as}/tests/utils.spec.ts | 0 .../contract_as}/tests/utils/helpers.ts | 0 .../contract_as}/tests/utils/spec.ts | 0 .../contracts/.cargo/config | 0 .../SRE/create-test-node-01/Cargo.toml | 0 .../SRE/create-test-node-01/src/main.rs | 0 .../SRE/create-test-node-02/Cargo.toml | 0 .../SRE/create-test-node-02/src/main.rs | 0 .../SRE/create-test-node-03/Cargo.toml | 0 .../SRE/create-test-node-03/src/main.rs | 0 .../SRE/create-test-node-shared/Cargo.toml | 0 .../SRE/create-test-node-shared/src/lib.rs | 0 .../bench/create-accounts/Cargo.toml | 0 .../bench/create-accounts/src/main.rs | 0 .../contracts/bench/create-purses/Cargo.toml | 0 .../contracts/bench/create-purses/src/main.rs | 0 .../transfer-to-existing-account/Cargo.toml | 0 .../transfer-to-existing-account/src/main.rs | 0 .../bench/transfer-to-purse/Cargo.toml | 0 .../bench/transfer-to-purse/src/main.rs | 0 .../contracts/client/bonding/Cargo.toml | 0 .../contracts/client/bonding/src/main.rs | 0 .../client/counter-define/Cargo.toml | 0 .../client/counter-define/src/main.rs | 0 .../client/named-purse-payment/Cargo.toml | 0 .../client/named-purse-payment/src/main.rs | 0 .../contracts/client/revert/Cargo.toml | 0 .../contracts/client/revert/src/main.rs | 0 .../transfer-to-account-stored/Cargo.toml | 0 .../transfer-to-account-stored/src/main.rs | 0 .../Cargo.toml | 0 .../src/main.rs | 0 .../transfer-to-account-u512/Cargo.toml | 0 .../transfer-to-account-u512/src/bin/main.rs | 0 .../transfer-to-account-u512/src/lib.rs | 0 .../client/transfer-to-account/Cargo.toml | 0 .../transfer-to-account/src/bin/main.rs | 0 .../client/transfer-to-account/src/lib.rs | 0 .../contracts/client/unbonding/Cargo.toml | 0 .../contracts/client/unbonding/src/main.rs | 0 .../explorer/faucet-stored/Cargo.toml | 0 .../explorer/faucet-stored/src/main.rs | 0 .../contracts/explorer/faucet/Cargo.toml | 0 .../contracts/explorer/faucet/src/bin/main.rs | 0 .../contracts/explorer/faucet/src/lib.rs | 0 .../integration/add-associated-key/Cargo.toml | 0 .../add-associated-key/src/main.rs | 0 .../integration/args-multi/Cargo.toml | 0 .../integration/args-multi/src/main.rs | 0 .../contracts/integration/args-u32/Cargo.toml | 0 .../integration/args-u32/src/main.rs | 0 .../integration/args-u512/Cargo.toml | 0 .../integration/args-u512/src/main.rs | 0 .../integration/create-named-purse/Cargo.toml | 0 .../create-named-purse/src/main.rs | 0 .../integration/direct-revert/Cargo.toml | 0 .../integration/direct-revert/src/main.rs | 0 .../integration/get-caller-call/Cargo.toml | 0 .../integration/get-caller-call/src/main.rs | 0 .../integration/get-caller-define/Cargo.toml | 0 .../integration/get-caller-define/src/main.rs | 0 .../list-known-urefs-call/Cargo.toml | 0 .../list-known-urefs-call/src/main.rs | 0 .../list-known-urefs-define/Cargo.toml | 0 .../list-known-urefs-define/src/main.rs | 0 .../payment-from-named-purse/Cargo.toml | 0 .../payment-from-named-purse/src/main.rs | 0 .../integration/set-key-thresholds/Cargo.toml | 0 .../set-key-thresholds/src/main.rs | 0 .../subcall-revert-call/Cargo.toml | 0 .../subcall-revert-call/src/main.rs | 0 .../subcall-revert-define/Cargo.toml | 0 .../subcall-revert-define/src/main.rs | 0 .../update-associated-key/Cargo.toml | 0 .../update-associated-key/src/main.rs | 0 .../integration/write-all-types/Cargo.toml | 0 .../integration/write-all-types/src/main.rs | 0 .../host-function-metrics/Cargo.toml | 0 .../host-function-metrics/src/lib.rs | 0 .../profiling/simple-transfer/Cargo.toml | 0 .../profiling/simple-transfer/src/main.rs | 0 .../profiling/state-initializer/Cargo.toml | 0 .../profiling/state-initializer/src/main.rs | 0 .../contracts/system/mint-install/Cargo.toml | 0 .../contracts/system/mint-install/src/main.rs | 0 .../contracts/system/mint-token/Cargo.toml | 0 .../system/mint-token/src/bin/main.rs | 0 .../contracts/system/mint-token/src/lib.rs | 0 .../contracts/system/pos-install/Cargo.toml | 0 .../contracts/system/pos-install/src/main.rs | 0 .../contracts/system/pos/Cargo.toml | 0 .../contracts/system/pos/src/bin/main.rs | 0 .../contracts/system/pos/src/lib.rs | 0 .../standard-payment-install/Cargo.toml | 0 .../standard-payment-install/src/main.rs | 0 .../system/standard-payment/Cargo.toml | 0 .../system/standard-payment/src/bin/main.rs | 0 .../system/standard-payment/src/lib.rs | 0 .../system/test-mint-token/Cargo.toml | 0 .../system/test-mint-token/src/main.rs | 0 .../contracts/test/add-gas-subcall/Cargo.toml | 0 .../test/add-gas-subcall/src/main.rs | 0 .../test/add-update-associated-key/Cargo.toml | 0 .../add-update-associated-key/src/main.rs | 0 .../contracts/test/authorized-keys/Cargo.toml | 0 .../test/authorized-keys/src/main.rs | 0 .../test/contract-context/Cargo.toml | 0 .../test/contract-context/src/main.rs | 0 .../contracts/test/create-purse-01/Cargo.toml | 0 .../test/create-purse-01/src/bin/main.rs | 0 .../contracts/test/create-purse-01/src/lib.rs | 0 .../test/deserialize-error/Cargo.toml | 0 .../test/deserialize-error/src/main.rs | 0 .../test/do-nothing-stored-caller/Cargo.toml | 0 .../test/do-nothing-stored-caller/src/main.rs | 0 .../do-nothing-stored-upgrader/Cargo.toml | 0 .../do-nothing-stored-upgrader/src/main.rs | 0 .../test/do-nothing-stored/Cargo.toml | 0 .../test/do-nothing-stored/src/main.rs | 0 .../contracts/test/do-nothing/Cargo.toml | 0 .../contracts/test/do-nothing/src/main.rs | 0 .../test/ee-221-regression/Cargo.toml | 0 .../test/ee-221-regression/src/main.rs | 0 .../test/ee-401-regression-call/Cargo.toml | 0 .../test/ee-401-regression-call/src/main.rs | 0 .../test/ee-401-regression/Cargo.toml | 0 .../test/ee-401-regression/src/main.rs | 0 .../test/ee-441-rng-state/Cargo.toml | 0 .../test/ee-441-rng-state/src/main.rs | 0 .../test/ee-460-regression/Cargo.toml | 0 .../test/ee-460-regression/src/main.rs | 0 .../test/ee-532-regression/Cargo.toml | 0 .../test/ee-532-regression/src/main.rs | 0 .../test/ee-536-regression/Cargo.toml | 0 .../test/ee-536-regression/src/main.rs | 0 .../test/ee-539-regression/Cargo.toml | 0 .../test/ee-539-regression/src/main.rs | 0 .../test/ee-549-regression/Cargo.toml | 0 .../test/ee-549-regression/src/main.rs | 0 .../test/ee-550-regression/Cargo.toml | 0 .../test/ee-550-regression/src/main.rs | 0 .../test/ee-572-regression-create/Cargo.toml | 0 .../test/ee-572-regression-create/src/main.rs | 0 .../ee-572-regression-escalate/Cargo.toml | 0 .../ee-572-regression-escalate/src/main.rs | 0 .../test/ee-584-regression/Cargo.toml | 0 .../test/ee-584-regression/src/main.rs | 0 .../test/ee-597-regression/Cargo.toml | 0 .../test/ee-597-regression/src/main.rs | 0 .../test/ee-598-regression/Cargo.toml | 0 .../test/ee-598-regression/src/main.rs | 0 .../test/ee-599-regression/Cargo.toml | 0 .../test/ee-599-regression/src/main.rs | 0 .../test/ee-601-regression/Cargo.toml | 0 .../test/ee-601-regression/src/main.rs | 0 .../test/ee-771-regression/Cargo.toml | 0 .../test/ee-771-regression/src/main.rs | 0 .../test/ee-803-regression/Cargo.toml | 0 .../test/ee-803-regression/src/main.rs | 0 .../contracts/test/endless-loop/Cargo.toml | 0 .../contracts/test/endless-loop/src/main.rs | 0 .../test/expensive-calculation/Cargo.toml | 0 .../test/expensive-calculation/src/main.rs | 0 .../contracts/test/get-arg/Cargo.toml | 0 .../contracts/test/get-arg/src/main.rs | 0 .../contracts/test/get-blocktime/Cargo.toml | 0 .../contracts/test/get-blocktime/src/main.rs | 0 .../test/get-caller-subcall/Cargo.toml | 0 .../test/get-caller-subcall/src/main.rs | 0 .../contracts/test/get-caller/Cargo.toml | 0 .../contracts/test/get-caller/src/main.rs | 0 .../test/get-phase-payment/Cargo.toml | 0 .../test/get-phase-payment/src/main.rs | 0 .../contracts/test/get-phase/Cargo.toml | 0 .../contracts/test/get-phase/src/main.rs | 0 .../contracts/test/groups/Cargo.toml | 0 .../contracts/test/groups/src/main.rs | 0 .../test/key-management-thresholds/Cargo.toml | 0 .../key-management-thresholds/src/main.rs | 0 .../contracts/test/list-named-keys/Cargo.toml | 0 .../test/list-named-keys/src/main.rs | 0 .../contracts/test/local-state-add/Cargo.toml | 0 .../test/local-state-add/src/main.rs | 0 .../test/local-state-stored-caller/Cargo.toml | 0 .../local-state-stored-caller/src/main.rs | 0 .../local-state-stored-upgraded/Cargo.toml | 0 .../src/bin/main.rs | 0 .../local-state-stored-upgraded/src/lib.rs | 0 .../local-state-stored-upgrader/Cargo.toml | 0 .../src/bin/main.rs | 0 .../test/local-state-stored/Cargo.toml | 0 .../test/local-state-stored/src/main.rs | 0 .../contracts/test/local-state/Cargo.toml | 0 .../test/local-state/src/bin/main.rs | 0 .../contracts/test/local-state/src/lib.rs | 0 .../contracts/test/main-purse/Cargo.toml | 0 .../contracts/test/main-purse/src/main.rs | 0 .../contracts/test/manage-groups/Cargo.toml | 0 .../contracts/test/manage-groups/src/main.rs | 0 .../test/measure-gas-subcall/Cargo.toml | 0 .../test/measure-gas-subcall/src/main.rs | 0 .../contracts/test/mint-purse/Cargo.toml | 0 .../contracts/test/mint-purse/src/main.rs | 0 .../test/modified-mint-caller/Cargo.toml | 0 .../test/modified-mint-caller/src/main.rs | 0 .../test/modified-mint-upgrader/Cargo.toml | 0 .../test/modified-mint-upgrader/src/main.rs | 0 .../contracts/test/modified-mint/Cargo.toml | 0 .../test/modified-mint/src/bin/main.rs | 0 .../contracts/test/modified-mint/src/lib.rs | 0 .../test/modified-system-upgrader/Cargo.toml | 0 .../test/modified-system-upgrader/src/main.rs | 0 .../contracts/test/named-keys/Cargo.toml | 0 .../contracts/test/named-keys/src/main.rs | 0 .../test/overwrite-uref-content/Cargo.toml | 0 .../test/overwrite-uref-content/src/main.rs | 0 .../contracts/test/pos-bonding/Cargo.toml | 0 .../contracts/test/pos-bonding/src/main.rs | 0 .../test/pos-finalize-payment/Cargo.toml | 0 .../test/pos-finalize-payment/src/main.rs | 0 .../test/pos-get-payment-purse/Cargo.toml | 0 .../test/pos-get-payment-purse/src/main.rs | 0 .../test/pos-refund-purse/Cargo.toml | 0 .../test/pos-refund-purse/src/main.rs | 0 .../purse-holder-stored-caller/Cargo.toml | 0 .../purse-holder-stored-caller/src/main.rs | 0 .../purse-holder-stored-upgrader/Cargo.toml | 0 .../purse-holder-stored-upgrader/src/main.rs | 0 .../test/purse-holder-stored/Cargo.toml | 0 .../test/purse-holder-stored/src/main.rs | 0 .../test/remove-associated-key/Cargo.toml | 0 .../test/remove-associated-key/src/main.rs | 0 .../test/test-payment-stored/Cargo.toml | 0 .../test/test-payment-stored/src/main.rs | 0 .../Cargo.toml | 0 .../src/main.rs | 0 .../Cargo.toml | 0 .../src/main.rs | 0 .../Cargo.toml | 0 .../src/main.rs | 0 .../test/transfer-purse-to-account/Cargo.toml | 0 .../transfer-purse-to-account/src/bin/main.rs | 0 .../test/transfer-purse-to-account/src/lib.rs | 0 .../test/transfer-purse-to-purse/Cargo.toml | 0 .../test/transfer-purse-to-purse/src/main.rs | 0 .../contracts_as}/.gitignore | 0 .../client/bonding/assembly/index.ts | 16 ++++----- .../client/bonding/assembly/tsconfig.json | 0 .../contracts_as}/client/bonding/index.js | 0 .../contracts_as}/client/bonding/package.json | 2 +- .../named-purse-payment/assembly/index.ts | 22 ++++++------ .../assembly/tsconfig.json | 0 .../client/named-purse-payment/index.js | 0 .../client/named-purse-payment}/package.json | 2 +- .../client/revert/assembly/index.ts | 2 +- .../client/revert/assembly/tsconfig.json | 0 .../contracts_as}/client/revert/index.js | 0 .../contracts_as/client/revert}/package.json | 2 +- .../assembly/index.ts | 10 +++--- .../assembly/tsconfig.json | 0 .../client/transfer-to-account-u512/index.js | 0 .../transfer-to-account-u512}/package.json | 2 +- .../client/unbonding/assembly/index.ts | 12 +++---- .../client/unbonding/assembly/tsconfig.json | 0 .../contracts_as}/client/unbonding/index.js | 0 .../client/unbonding}/package.json | 2 +- .../assembly/index.ts | 10 +++--- .../assembly/tsconfig.json | 0 .../test/add-update-associated-key/index.js | 0 .../add-update-associated-key/package.json | 2 +- .../test/authorized-keys/assembly/index.ts | 12 +++---- .../authorized-keys/assembly/tsconfig.json | 0 .../test/authorized-keys/index.js | 0 .../test/authorized-keys}/package.json | 2 +- .../test/create-purse-01/assembly/index.ts | 14 ++++---- .../create-purse-01/assembly/tsconfig.json | 0 .../test/create-purse-01/index.js | 0 .../test/create-purse-01}/package.json | 2 +- .../assembly/index.ts | 14 ++++---- .../assembly/tsconfig.json | 0 .../test/do-nothing-stored-caller/index.js | 0 .../do-nothing-stored-caller}/package.json | 2 +- .../assembly/index.ts | 18 +++++----- .../assembly/tsconfig.json | 0 .../test/do-nothing-stored-upgrader/index.js | 0 .../do-nothing-stored-upgrader/package.json | 2 +- .../test/do-nothing-stored/assembly/index.ts | 12 +++---- .../do-nothing-stored/assembly/tsconfig.json | 0 .../test/do-nothing-stored/index.js | 0 .../test/do-nothing-stored/package.json | 2 +- .../test/do-nothing/assembly/index.ts | 0 .../test/do-nothing/assembly/tsconfig.json | 0 .../contracts_as}/test/do-nothing/index.js | 0 .../test/do-nothing}/package.json | 2 +- .../test/endless-loop/assembly/index.ts | 2 +- .../test/endless-loop/assembly/tsconfig.json | 0 .../contracts_as}/test/endless-loop/index.js | 0 .../test/endless-loop/package.json | 2 +- .../test/get-arg/assembly/index.ts | 8 ++--- .../test/get-arg/assembly/tsconfig.json | 0 .../contracts_as}/test/get-arg/index.js | 0 .../contracts_as}/test/get-arg/package.json | 2 +- .../test/get-blocktime/assembly/index.ts | 6 ++-- .../test/get-blocktime/assembly/tsconfig.json | 0 .../contracts_as}/test/get-blocktime/index.js | 0 .../test/get-blocktime/package.json | 2 +- .../test/get-caller/assembly/index.ts | 8 ++--- .../test/get-caller/assembly/tsconfig.json | 0 .../contracts_as}/test/get-caller/index.js | 0 .../test/get-caller}/package.json | 2 +- .../test/get-phase-payment/assembly/index.ts | 14 ++++---- .../get-phase-payment/assembly/tsconfig.json | 0 .../test/get-phase-payment/index.js | 0 .../test/get-phase-payment/package.json | 2 +- .../test/get-phase/assembly/index.ts | 4 +-- .../test/get-phase/assembly/tsconfig.json | 0 .../contracts_as}/test/get-phase/index.js | 0 .../contracts_as/test/get-phase}/package.json | 2 +- .../test/groups/assembly/index.ts | 20 +++++------ .../test/groups/assembly/tsconfig.json | 0 .../contracts_as}/test/groups/index.js | 0 .../contracts_as/test/groups}/package.json | 2 +- .../assembly/index.ts | 12 +++---- .../assembly/tsconfig.json | 0 .../test/key-management-thresholds/index.js | 0 .../key-management-thresholds/package.json | 2 +- .../test/list-named-keys/assembly/index.ts | 10 +++--- .../list-named-keys/assembly/tsconfig.json | 0 .../test/list-named-keys/index.js | 0 .../test/list-named-keys/package.json | 2 +- .../test/main-purse/assembly/index.ts | 8 ++--- .../test/main-purse/assembly/tsconfig.json | 0 .../contracts_as}/test/main-purse/index.js | 0 .../test/main-purse/package.json | 2 +- .../test/manage-groups/assembly/index.ts | 16 ++++----- .../test/manage-groups/assembly/tsconfig.json | 0 .../contracts_as}/test/manage-groups/index.js | 0 .../test/manage-groups/package.json | 2 +- .../test/named-keys/assembly/index.ts | 12 +++---- .../test/named-keys/assembly/tsconfig.json | 0 .../contracts_as}/test/named-keys/index.js | 0 .../test/named-keys/package.json | 2 +- .../overwrite-uref-content/assembly/index.ts | 10 +++--- .../assembly/tsconfig.json | 0 .../test/overwrite-uref-content/index.js | 0 .../test/overwrite-uref-content/package.json | 2 +- .../assembly/index.ts | 16 ++++----- .../assembly/tsconfig.json | 0 .../test/purse-holder-stored-caller/index.js | 0 .../purse-holder-stored-caller/package.json | 2 +- .../assembly/index.ts | 18 +++++----- .../assembly/tsconfig.json | 0 .../purse-holder-stored-upgrader/index.js | 0 .../purse-holder-stored-upgrader/package.json | 2 +- .../purse-holder-stored/assembly/index.ts | 20 +++++------ .../assembly/tsconfig.json | 0 .../test/purse-holder-stored/index.js | 0 .../test/purse-holder-stored}/package.json | 2 +- .../remove-associated-key/assembly/index.ts | 10 +++--- .../assembly/tsconfig.json | 0 .../test/remove-associated-key/index.js | 0 .../test/remove-associated-key/package.json | 2 +- .../assembly/index.ts | 18 +++++----- .../assembly/tsconfig.json | 0 .../transfer-main-purse-to-new-purse/index.js | 0 .../package.json | 2 +- .../assembly/index.ts | 22 ++++++------ .../assembly/tsconfig.json | 0 .../transfer-purse-to-account-stored/index.js | 0 .../package.json | 2 +- .../assembly/index.ts | 18 +++++----- .../assembly/tsconfig.json | 0 .../test/transfer-purse-to-account/index.js | 0 .../transfer-purse-to-account/package.json | 2 +- .../transfer-purse-to-purse/assembly/index.ts | 20 +++++------ .../assembly/tsconfig.json | 0 .../test/transfer-purse-to-purse/index.js | 0 .../test/transfer-purse-to-purse/package.json | 2 +- 622 files changed, 655 insertions(+), 602 deletions(-) create mode 100644 client/Cargo.toml rename {node/src/apps/client => client/src}/main.rs (100%) rename grpc/{test-support => test_support}/Cargo.toml (95%) rename grpc/{test-support => test_support}/README.md (100%) rename grpc/{test-support => test_support}/src/account.rs (75%) rename grpc/{test-support => test_support}/src/code.rs (100%) rename grpc/{test-support => test_support}/src/error.rs (92%) rename grpc/{test-support => test_support}/src/internal/additive_map_diff.rs (98%) rename grpc/{test-support => test_support}/src/internal/deploy_item_builder.rs (99%) rename grpc/{test-support => test_support}/src/internal/exec_with_return.rs (94%) rename grpc/{test-support => test_support}/src/internal/execute_request_builder.rs (96%) rename grpc/{test-support => test_support}/src/internal/mod.rs (93%) rename grpc/{test-support => test_support}/src/internal/upgrade_request_builder.rs (98%) rename grpc/{test-support => test_support}/src/internal/utils.rs (98%) rename grpc/{test-support => test_support}/src/internal/wasm_test_builder.rs (99%) rename grpc/{test-support => test_support}/src/lib.rs (100%) rename grpc/{test-support => test_support}/src/session.rs (98%) rename grpc/{test-support => test_support}/src/test_context.rs (98%) rename grpc/{test-support => test_support}/src/value.rs (93%) rename grpc/{test-support => test_support}/tests/version_numbers.rs (100%) rename node/src/{apps/node => app}/cli.rs (100%) rename node/src/{apps/node => app}/config.rs (100%) rename node/src/{apps/node => app}/logging.rs (100%) rename node/src/{apps/node => app}/main.rs (100%) rename node/src/{contract_core.rs => components/contract_runtime/core.rs} (100%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/deploy_item.rs (95%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/engine_config.rs (100%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/error.rs (88%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/executable_deploy_item.rs (96%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/execute_request.rs (93%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/execution_effect.rs (77%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/execution_result.rs (98%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/genesis.rs (95%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/mod.rs (99%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/op.rs (100%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/query.rs (85%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/run_genesis_request.rs (94%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/system_contract_cache.rs (99%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/transfer.rs (96%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/upgrade.rs (91%) rename node/src/{contract_core => components/contract_runtime/core}/engine_state/utils.rs (100%) rename node/src/{contract_core => components/contract_runtime/core}/execution/address_generator.rs (97%) rename node/src/{contract_core => components/contract_runtime/core}/execution/error.rs (93%) rename node/src/{contract_core => components/contract_runtime/core}/execution/executor.rs (99%) rename node/src/{contract_core => components/contract_runtime/core}/execution/mod.rs (100%) rename node/src/{contract_core => components/contract_runtime/core}/execution/tests.rs (93%) rename node/src/{contract_core => components/contract_runtime/core}/resolvers/error.rs (100%) rename node/src/{contract_core => components/contract_runtime/core}/resolvers/memory_resolver.rs (100%) rename node/src/{contract_core => components/contract_runtime/core}/resolvers/mod.rs (91%) rename node/src/{contract_core => components/contract_runtime/core}/resolvers/v1_function_index.rs (100%) rename node/src/{contract_core => components/contract_runtime/core}/resolvers/v1_resolver.rs (100%) rename node/src/{contract_core => components/contract_runtime/core}/runtime/args.rs (100%) rename node/src/{contract_core => components/contract_runtime/core}/runtime/externals.rs (99%) rename node/src/{contract_core => components/contract_runtime/core}/runtime/mint_internal.rs (92%) rename node/src/{contract_core => components/contract_runtime/core}/runtime/mod.rs (99%) rename node/src/{contract_core => components/contract_runtime/core}/runtime/proof_of_stake_internal.rs (96%) rename node/src/{contract_core => components/contract_runtime/core}/runtime/scoped_instrumenter.rs (97%) rename node/src/{contract_core => components/contract_runtime/core}/runtime/standard_payment_internal.rs (90%) rename node/src/{contract_core => components/contract_runtime/core}/runtime_context/mod.rs (99%) rename node/src/{contract_core => components/contract_runtime/core}/runtime_context/tests.rs (99%) rename node/src/{contract_core => components/contract_runtime/core}/tracking_copy/byte_size.rs (97%) rename node/src/{contract_core => components/contract_runtime/core}/tracking_copy/ext.rs (96%) rename node/src/{contract_core => components/contract_runtime/core}/tracking_copy/meter.rs (86%) rename node/src/{contract_core => components/contract_runtime/core}/tracking_copy/mod.rs (98%) rename node/src/{contract_core => components/contract_runtime/core}/tracking_copy/tests.rs (99%) rename node/src/{contract_shared.rs => components/contract_runtime/shared.rs} (100%) rename node/src/{contract_shared => components/contract_runtime/shared}/account.rs (99%) rename node/src/{contract_shared => components/contract_runtime/shared}/account/action_thresholds.rs (100%) rename node/src/{contract_shared => components/contract_runtime/shared}/account/associated_keys.rs (100%) rename node/src/{contract_shared => components/contract_runtime/shared}/additive_map.rs (98%) rename node/src/{contract_shared => components/contract_runtime/shared}/gas.rs (97%) rename node/src/{contract_shared => components/contract_runtime/shared}/logging/README.md (100%) rename node/src/{contract_shared => components/contract_runtime/shared}/logging/mod.rs (97%) rename node/src/{contract_shared => components/contract_runtime/shared}/logging/settings.rs (100%) rename node/src/{contract_shared => components/contract_runtime/shared}/logging/structured_message.rs (99%) rename node/src/{contract_shared => components/contract_runtime/shared}/logging/terminal_logger.rs (98%) rename node/src/{contract_shared => components/contract_runtime/shared}/motes.rs (97%) rename node/src/{contract_shared => components/contract_runtime/shared}/newtypes/macros.rs (100%) rename node/src/{contract_shared => components/contract_runtime/shared}/newtypes/mod.rs (99%) rename node/src/{contract_shared => components/contract_runtime/shared}/page_size.rs (100%) rename node/src/{contract_shared => components/contract_runtime/shared}/socket.rs (100%) rename node/src/{contract_shared => components/contract_runtime/shared}/stored_value.rs (97%) rename node/src/{contract_shared => components/contract_runtime/shared}/test_utils.rs (86%) rename node/src/{contract_shared => components/contract_runtime/shared}/transform.rs (98%) rename node/src/{contract_shared => components/contract_runtime/shared}/type_mismatch.rs (100%) rename node/src/{contract_shared => components/contract_runtime/shared}/utils.rs (100%) rename node/src/{contract_shared => components/contract_runtime/shared}/wasm.rs (89%) rename node/src/{contract_shared => components/contract_runtime/shared}/wasm_costs.rs (97%) rename node/src/{contract_shared => components/contract_runtime/shared}/wasm_prep.rs (96%) rename node/src/{contract_storage.rs => components/contract_runtime/storage.rs} (85%) rename node/src/{contract_storage => components/contract_runtime/storage}/error/in_memory.rs (100%) rename node/src/{contract_storage => components/contract_runtime/storage}/error/lmdb.rs (100%) rename node/src/{contract_storage => components/contract_runtime/storage}/error/mod.rs (100%) rename node/src/{contract_storage => components/contract_runtime/storage}/global_state/in_memory.rs (99%) rename node/src/{contract_storage => components/contract_runtime/storage}/global_state/lmdb.rs (98%) rename node/src/{contract_storage => components/contract_runtime/storage}/global_state/mod.rs (98%) rename node/src/{contract_storage => components/contract_runtime/storage}/protocol_data.rs (97%) rename node/src/{contract_storage => components/contract_runtime/storage}/protocol_data_store/in_memory.rs (94%) rename node/src/{contract_storage => components/contract_runtime/storage}/protocol_data_store/lmdb.rs (96%) rename node/src/{contract_storage => components/contract_runtime/storage}/protocol_data_store/mod.rs (80%) rename node/src/{contract_storage => components/contract_runtime/storage}/protocol_data_store/tests/mod.rs (100%) rename node/src/{contract_storage => components/contract_runtime/storage}/protocol_data_store/tests/proptests.rs (97%) rename node/src/{contract_storage => components/contract_runtime/storage}/store/mod.rs (92%) rename node/src/{contract_storage => components/contract_runtime/storage}/store/store_ext.rs (95%) rename node/src/{contract_storage => components/contract_runtime/storage}/store/tests.rs (96%) rename node/src/{contract_storage => components/contract_runtime/storage}/transaction_source/in_memory.rs (98%) rename node/src/{contract_storage => components/contract_runtime/storage}/transaction_source/lmdb.rs (98%) rename node/src/{contract_storage => components/contract_runtime/storage}/transaction_source/mod.rs (100%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie/gens.rs (96%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie/mod.rs (97%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie/tests.rs (90%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/in_memory.rs (86%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/lmdb.rs (87%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/mod.rs (74%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/operations/mod.rs (99%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/operations/tests/ee_699.rs (99%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/operations/tests/keys.rs (94%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/operations/tests/mod.rs (99%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/operations/tests/proptests.rs (100%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/operations/tests/read.rs (98%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/operations/tests/scan.rs (97%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/operations/tests/write.rs (100%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/tests/concurrent.rs (98%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/tests/mod.rs (93%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/tests/proptests.rs (89%) rename node/src/{contract_storage => components/contract_runtime/storage}/trie_store/tests/simple.rs (99%) rename {smart-contracts => smart_contracts}/contract/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contract/README.md (100%) rename {smart-contracts => smart_contracts}/contract/src/contract_api/account.rs (100%) rename {smart-contracts => smart_contracts}/contract/src/contract_api/mod.rs (100%) rename {smart-contracts => smart_contracts}/contract/src/contract_api/runtime.rs (100%) rename {smart-contracts => smart_contracts}/contract/src/contract_api/storage.rs (100%) rename {smart-contracts => smart_contracts}/contract/src/contract_api/system.rs (100%) rename {smart-contracts => smart_contracts}/contract/src/ext_ffi.rs (100%) rename {smart-contracts => smart_contracts}/contract/src/handlers.rs (100%) rename {smart-contracts => smart_contracts}/contract/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contract/src/unwrap_or_revert.rs (100%) rename {smart-contracts => smart_contracts}/contract/tests/version_numbers.rs (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/.gitignore (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/.npmignore (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/.npmrc (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/README.md (97%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/account.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/bignum.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/bytesrepr.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/clvalue.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/constants.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/contracts.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/error.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/externals.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/index.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/key.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/local.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/option.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/pair.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/purse.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/runtime_args.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/tsconfig.json (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/unit.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/uref.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/assembly/utils.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/build/.gitignore (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/index.js (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/package-lock.json (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/package.json (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/tests/assembly/bignum.spec.as.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/tests/assembly/bytesrepr.spec.as.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/tests/assembly/runtime_args.spec.as.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/tests/assembly/utils.spec.as.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/tests/bignum.spec.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/tests/bytesrepr.spec.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/tests/runtime_args.spec.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/tests/tsconfig.json (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/tests/utils.spec.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/tests/utils/helpers.ts (100%) rename {smart-contracts/contract-as => smart_contracts/contract_as}/tests/utils/spec.ts (100%) rename {smart-contracts => smart_contracts}/contracts/.cargo/config (100%) rename {smart-contracts => smart_contracts}/contracts/SRE/create-test-node-01/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/SRE/create-test-node-01/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/SRE/create-test-node-02/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/SRE/create-test-node-02/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/SRE/create-test-node-03/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/SRE/create-test-node-03/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/SRE/create-test-node-shared/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/SRE/create-test-node-shared/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/bench/create-accounts/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/bench/create-accounts/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/bench/create-purses/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/bench/create-purses/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/bench/transfer-to-existing-account/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/bench/transfer-to-existing-account/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/bench/transfer-to-purse/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/bench/transfer-to-purse/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/client/bonding/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/client/bonding/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/client/counter-define/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/client/counter-define/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/client/named-purse-payment/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/client/named-purse-payment/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/client/revert/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/client/revert/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/client/transfer-to-account-stored/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/client/transfer-to-account-stored/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/client/transfer-to-account-u512-stored/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/client/transfer-to-account-u512-stored/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/client/transfer-to-account-u512/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/client/transfer-to-account-u512/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/client/transfer-to-account-u512/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/client/transfer-to-account/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/client/transfer-to-account/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/client/transfer-to-account/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/client/unbonding/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/client/unbonding/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/explorer/faucet-stored/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/explorer/faucet-stored/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/explorer/faucet/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/explorer/faucet/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/explorer/faucet/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/add-associated-key/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/add-associated-key/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/args-multi/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/args-multi/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/args-u32/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/args-u32/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/args-u512/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/args-u512/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/create-named-purse/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/create-named-purse/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/direct-revert/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/direct-revert/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/get-caller-call/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/get-caller-call/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/get-caller-define/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/get-caller-define/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/list-known-urefs-call/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/list-known-urefs-call/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/list-known-urefs-define/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/list-known-urefs-define/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/payment-from-named-purse/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/payment-from-named-purse/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/set-key-thresholds/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/set-key-thresholds/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/subcall-revert-call/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/subcall-revert-call/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/subcall-revert-define/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/subcall-revert-define/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/update-associated-key/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/update-associated-key/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/integration/write-all-types/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/integration/write-all-types/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/profiling/host-function-metrics/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/profiling/host-function-metrics/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/profiling/simple-transfer/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/profiling/simple-transfer/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/profiling/state-initializer/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/profiling/state-initializer/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/system/mint-install/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/system/mint-install/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/system/mint-token/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/system/mint-token/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/system/mint-token/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/system/pos-install/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/system/pos-install/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/system/pos/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/system/pos/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/system/pos/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/system/standard-payment-install/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/system/standard-payment-install/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/system/standard-payment/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/system/standard-payment/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/system/standard-payment/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/system/test-mint-token/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/system/test-mint-token/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/add-gas-subcall/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/add-gas-subcall/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/add-update-associated-key/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/add-update-associated-key/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/authorized-keys/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/authorized-keys/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/contract-context/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/contract-context/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/create-purse-01/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/create-purse-01/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/create-purse-01/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/deserialize-error/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/deserialize-error/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/do-nothing-stored-caller/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/do-nothing-stored-caller/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/do-nothing-stored-upgrader/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/do-nothing-stored-upgrader/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/do-nothing-stored/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/do-nothing-stored/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/do-nothing/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/do-nothing/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-221-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-221-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-401-regression-call/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-401-regression-call/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-401-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-401-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-441-rng-state/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-441-rng-state/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-460-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-460-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-532-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-532-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-536-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-536-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-539-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-539-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-549-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-549-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-550-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-550-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-572-regression-create/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-572-regression-create/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-572-regression-escalate/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-572-regression-escalate/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-584-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-584-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-597-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-597-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-598-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-598-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-599-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-599-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-601-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-601-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-771-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-771-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-803-regression/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/ee-803-regression/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/endless-loop/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/endless-loop/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/expensive-calculation/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/expensive-calculation/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-arg/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-arg/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-blocktime/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-blocktime/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-caller-subcall/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-caller-subcall/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-caller/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-caller/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-phase-payment/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-phase-payment/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-phase/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/get-phase/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/groups/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/groups/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/key-management-thresholds/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/key-management-thresholds/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/list-named-keys/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/list-named-keys/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state-add/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state-add/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state-stored-caller/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state-stored-caller/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state-stored-upgraded/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state-stored-upgraded/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state-stored-upgraded/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state-stored-upgrader/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state-stored-upgrader/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state-stored/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state-stored/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/local-state/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/main-purse/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/main-purse/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/manage-groups/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/manage-groups/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/measure-gas-subcall/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/measure-gas-subcall/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/mint-purse/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/mint-purse/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/modified-mint-caller/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/modified-mint-caller/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/modified-mint-upgrader/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/modified-mint-upgrader/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/modified-mint/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/modified-mint/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/modified-mint/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/modified-system-upgrader/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/modified-system-upgrader/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/named-keys/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/named-keys/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/overwrite-uref-content/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/overwrite-uref-content/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/pos-bonding/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/pos-bonding/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/pos-finalize-payment/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/pos-finalize-payment/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/pos-get-payment-purse/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/pos-get-payment-purse/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/pos-refund-purse/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/pos-refund-purse/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/purse-holder-stored-caller/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/purse-holder-stored-caller/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/purse-holder-stored-upgrader/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/purse-holder-stored-upgrader/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/purse-holder-stored/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/purse-holder-stored/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/remove-associated-key/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/remove-associated-key/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/test-payment-stored/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/test-payment-stored/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/transfer-main-purse-to-new-purse/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/transfer-main-purse-to-new-purse/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/transfer-main-purse-to-two-purses/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/transfer-main-purse-to-two-purses/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/transfer-purse-to-account-stored/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/transfer-purse-to-account-stored/src/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/transfer-purse-to-account/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/transfer-purse-to-account/src/bin/main.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/transfer-purse-to-account/src/lib.rs (100%) rename {smart-contracts => smart_contracts}/contracts/test/transfer-purse-to-purse/Cargo.toml (100%) rename {smart-contracts => smart_contracts}/contracts/test/transfer-purse-to-purse/src/main.rs (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/.gitignore (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/bonding/assembly/index.ts (72%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/bonding/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/bonding/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/bonding/package.json (74%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/named-purse-payment/assembly/index.ts (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/named-purse-payment/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/named-purse-payment/index.js (100%) rename {smart-contracts/contracts-as/test/purse-holder-stored => smart_contracts/contracts_as/client/named-purse-payment}/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/revert/assembly/index.ts (54%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/revert/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/revert/index.js (100%) rename {smart-contracts/contracts-as/test/groups => smart_contracts/contracts_as/client/revert}/package.json (74%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/transfer-to-account-u512/assembly/index.ts (72%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/transfer-to-account-u512/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/transfer-to-account-u512/index.js (100%) rename {smart-contracts/contracts-as/test/do-nothing-stored-caller => smart_contracts/contracts_as/client/transfer-to-account-u512}/package.json (76%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/unbonding/assembly/index.ts (66%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/unbonding/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/client/unbonding/index.js (100%) rename {smart-contracts/contracts-as/test/get-phase => smart_contracts/contracts_as/client/unbonding}/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/add-update-associated-key/assembly/index.ts (72%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/add-update-associated-key/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/add-update-associated-key/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/add-update-associated-key/package.json (76%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/authorized-keys/assembly/index.ts (80%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/authorized-keys/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/authorized-keys/index.js (100%) rename {smart-contracts/contracts-as/test/create-purse-01 => smart_contracts/contracts_as/test/authorized-keys}/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/create-purse-01/assembly/index.ts (57%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/create-purse-01/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/create-purse-01/index.js (100%) rename {smart-contracts/contracts-as/test/authorized-keys => smart_contracts/contracts_as/test/create-purse-01}/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing-stored-caller/assembly/index.ts (66%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing-stored-caller/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing-stored-caller/index.js (100%) rename {smart-contracts/contracts-as/client/transfer-to-account-u512 => smart_contracts/contracts_as/test/do-nothing-stored-caller}/package.json (76%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing-stored-upgrader/assembly/index.ts (71%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing-stored-upgrader/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing-stored-upgrader/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing-stored-upgrader/package.json (76%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing-stored/assembly/index.ts (72%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing-stored/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing-stored/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing-stored/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing/assembly/index.ts (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/do-nothing/index.js (100%) rename {smart-contracts/contracts-as/test/get-caller => smart_contracts/contracts_as/test/do-nothing}/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/endless-loop/assembly/index.ts (55%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/endless-loop/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/endless-loop/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/endless-loop/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-arg/assembly/index.ts (78%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-arg/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-arg/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-arg/package.json (74%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-blocktime/assembly/index.ts (69%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-blocktime/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-blocktime/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-blocktime/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-caller/assembly/index.ts (71%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-caller/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-caller/index.js (100%) rename {smart-contracts/contracts-as/test/do-nothing => smart_contracts/contracts_as/test/get-caller}/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-phase-payment/assembly/index.ts (72%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-phase-payment/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-phase-payment/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-phase-payment/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-phase/assembly/index.ts (73%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-phase/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/get-phase/index.js (100%) rename {smart-contracts/contracts-as/client/unbonding => smart_contracts/contracts_as/test/get-phase}/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/groups/assembly/index.ts (93%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/groups/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/groups/index.js (100%) rename {smart-contracts/contracts-as/client/revert => smart_contracts/contracts_as/test/groups}/package.json (74%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/key-management-thresholds/assembly/index.ts (91%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/key-management-thresholds/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/key-management-thresholds/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/key-management-thresholds/package.json (76%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/list-named-keys/assembly/index.ts (90%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/list-named-keys/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/list-named-keys/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/list-named-keys/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/main-purse/assembly/index.ts (73%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/main-purse/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/main-purse/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/main-purse/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/manage-groups/assembly/index.ts (93%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/manage-groups/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/manage-groups/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/manage-groups/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/named-keys/assembly/index.ts (93%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/named-keys/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/named-keys/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/named-keys/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/overwrite-uref-content/assembly/index.ts (68%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/overwrite-uref-content/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/overwrite-uref-content/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/overwrite-uref-content/package.json (76%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/purse-holder-stored-caller/assembly/index.ts (77%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/purse-holder-stored-caller/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/purse-holder-stored-caller/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/purse-holder-stored-caller/package.json (76%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/purse-holder-stored-upgrader/assembly/index.ts (83%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/purse-holder-stored-upgrader/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/purse-holder-stored-upgrader/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/purse-holder-stored-upgrader/package.json (76%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/purse-holder-stored/assembly/index.ts (76%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/purse-holder-stored/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/purse-holder-stored/index.js (100%) rename {smart-contracts/contracts-as/client/named-purse-payment => smart_contracts/contracts_as/test/purse-holder-stored}/package.json (75%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/remove-associated-key/assembly/index.ts (67%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/remove-associated-key/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/remove-associated-key/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/remove-associated-key/package.json (76%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-main-purse-to-new-purse/assembly/index.ts (71%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-main-purse-to-new-purse/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-main-purse-to-new-purse/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-main-purse-to-new-purse/package.json (77%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-account-stored/assembly/index.ts (67%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-account-stored/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-account-stored/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-account-stored/package.json (77%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-account/assembly/index.ts (77%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-account/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-account/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-account/package.json (76%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-purse/assembly/index.ts (87%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-purse/assembly/tsconfig.json (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-purse/index.js (100%) rename {smart-contracts/contracts-as => smart_contracts/contracts_as}/test/transfer-purse-to-purse/package.json (76%) diff --git a/.gitignore b/.gitignore index 7f3e8e0a1a..a4d16a011a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -target-as +target_as # Criterion puts results in wrong directories inside workspace: https://github.com/bheisler/criterion.rs/issues/192 target diff --git a/Cargo.lock b/Cargo.lock index 80e4ad1c11..78713a477f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,6 +306,18 @@ dependencies = [ "loom", ] +[[package]] +name = "casperlabs-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "casperlabs-node", + "rand 0.7.3", + "reqwest", + "structopt", + "tokio 0.2.21", +] + [[package]] name = "casperlabs-contract" version = "0.6.0" @@ -425,7 +437,6 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_xorshift 0.2.0", - "reqwest", "rmp-serde", "serde", "serde-big-array", diff --git a/Cargo.toml b/Cargo.toml index 927736f099..33396e6250 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,24 @@ [workspace] members = [ - "smart-contracts/contract", - "smart-contracts/contracts/[!.]*/*", + "smart_contracts/contract", + "smart_contracts/contracts/[!.]*/*", "grpc/server", - "grpc/test-support", + "grpc/test_support", "grpc/tests", "types", "node", + "client", ] default-members = [ - "smart-contracts/contract", + "smart_contracts/contract", "grpc/server", - "grpc/test-support", + "grpc/test_support", "grpc/tests", "types", "node", + "client", ] # Include debug symbols in the release build of `casperlabs-engine-tests` so that `simple-transfer` will yield useful diff --git a/Makefile b/Makefile index 637bce4b00..d07d833f3f 100644 --- a/Makefile +++ b/Makefile @@ -11,14 +11,14 @@ EE_DIR = $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) # Rust Contracts # Directory names should match crate names -BENCH = $(shell find ./smart-contracts/contracts/bench -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) -CLIENT = $(shell find ./smart-contracts/contracts/client -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) -EXPLORER = $(shell find ./smart-contracts/contracts/explorer -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) -INTEGRATION = $(shell find ./smart-contracts/contracts/integration -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) -PROFILING = $(shell find ./smart-contracts/contracts/profiling -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) -SRE = $(shell find ./smart-contracts/contracts/SRE -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) -SYSTEM = $(shell find ./smart-contracts/contracts/system -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) -TEST = $(shell find ./smart-contracts/contracts/test -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +BENCH = $(shell find ./smart_contracts/contracts/bench -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +CLIENT = $(shell find ./smart_contracts/contracts/client -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +EXPLORER = $(shell find ./smart_contracts/contracts/explorer -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +INTEGRATION = $(shell find ./smart_contracts/contracts/integration -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +PROFILING = $(shell find ./smart_contracts/contracts/profiling -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +SRE = $(shell find ./smart_contracts/contracts/SRE -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +SYSTEM = $(shell find ./smart_contracts/contracts/system -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) +TEST = $(shell find ./smart_contracts/contracts/test -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) BENCH_CONTRACTS := $(patsubst %, build-contract-rs/%, $(BENCH)) CLIENT_CONTRACTS := $(patsubst %, build-contract-rs/%, $(CLIENT)) @@ -28,8 +28,8 @@ SRE_CONTRACTS := $(patsubst %, build-contract-rs/%, $(SRE)) TEST_CONTRACTS := $(patsubst %, build-contract-rs/%, $(TEST)) # AssemblyScript Contracts -CLIENT_CONTRACTS_AS = $(shell find ./smart-contracts/contracts-as/client -mindepth 1 -maxdepth 1 -type d) -TEST_CONTRACTS_AS = $(shell find ./smart-contracts/contracts-as/test -mindepth 1 -maxdepth 1 -type d) +CLIENT_CONTRACTS_AS = $(shell find ./smart_contracts/contracts_as/client -mindepth 1 -maxdepth 1 -type d) +TEST_CONTRACTS_AS = $(shell find ./smart_contracts/contracts_as/test -mindepth 1 -maxdepth 1 -type d) CLIENT_CONTRACTS_AS := $(patsubst %, build-contract-as/%, $(CLIENT_CONTRACTS_AS)) TEST_CONTRACTS_AS := $(patsubst %, build-contract-as/%, $(TEST_CONTRACTS_AS)) @@ -51,7 +51,7 @@ SYSTEM_CONTRACTS := $(patsubst %, build-contract-rs/%, SYSTEM_CONTRACTS_FEATURED := $(patsubst %, build-system-contract-featured-rs/%, $(SYSTEM)) CONTRACT_TARGET_DIR = target/wasm32-unknown-unknown/release -CONTRACT_TARGET_DIR_AS = target-as +CONTRACT_TARGET_DIR_AS = target_as PACKAGED_SYSTEM_CONTRACTS = mint_install.wasm pos_install.wasm standard_payment_install.wasm TOOL_TARGET_DIR = cargo-casperlabs/target TOOL_WASM_DIR = cargo-casperlabs/wasm @@ -134,7 +134,7 @@ test-rs: .PHONY: test-as test-as: setup-as - cd contract-as && npm run asbuild && npm run test + cd contract_as && npm run asbuild && npm run test .PHONY: test test: test-rs test-as @@ -149,13 +149,13 @@ test-contracts-enable-bonding-rs: build-contracts-enable-bonding-rs $(CARGO) test $(CARGO_FLAGS) --manifest-path "grpc/tests/Cargo.toml" --features "enable-bonding" -- --ignored --nocapture $(CARGO) test $(CARGO_FLAGS) --manifest-path "grpc/tests/Cargo.toml" --features "enable-bonding,use-system-contracts" -- --ignored --nocapture -.PHONY: test-contracts-as -test-contracts-as: build-contracts-rs build-contracts-as +.PHONY: test-contracts_as +test-contracts_as: build-contracts-rs build-contracts-as @# see https://github.com/rust-lang/cargo/issues/5015#issuecomment-515544290 $(CARGO) test $(CARGO_FLAGS) --manifest-path "grpc/tests/Cargo.toml" --features "use-as-wasm" -- --ignored --nocapture .PHONY: test-contracts -test-contracts: test-contracts-rs test-contracts-as +test-contracts: test-contracts-rs test-contracts_as .PHONY: check-format check-format: @@ -264,8 +264,8 @@ setup-nightly-rs: RUST_TOOLCHAIN := nightly setup-nightly-rs: setup-rs .PHONY: setup-as -setup-as: contract-as/package.json - cd contract-as && $(NPM) ci +setup-as: contract_as/package.json + cd contract_as && $(NPM) ci .PHONY: setup setup: setup-rs setup-as diff --git a/client/Cargo.toml b/client/Cargo.toml new file mode 100644 index 0000000000..65431e79b8 --- /dev/null +++ b/client/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "casperlabs-client" +version = "0.1.0" +authors = ["Marc Brinkmann ", "Fraser Hutchison "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.7.3" +reqwest = "0.10.6" +structopt = "0.3.14" +tokio = { version = "0.2.20", features = ["macros", "rt-threaded", "sync", "tcp", "time", "blocking"] } +casperlabs_node = { path = "../node", package = "casperlabs-node" } +anyhow = "1.0.28" \ No newline at end of file diff --git a/node/src/apps/client/main.rs b/client/src/main.rs similarity index 100% rename from node/src/apps/client/main.rs rename to client/src/main.rs diff --git a/grpc/server/src/engine_server/mappings/ipc/deploy_item.rs b/grpc/server/src/engine_server/mappings/ipc/deploy_item.rs index a1dc0a147f..af7d2a34a9 100644 --- a/grpc/server/src/engine_server/mappings/ipc/deploy_item.rs +++ b/grpc/server/src/engine_server/mappings/ipc/deploy_item.rs @@ -3,7 +3,7 @@ use std::{ convert::{TryFrom, TryInto}, }; -use node::contract_core::engine_state::deploy_item::DeployItem; +use node::components::contract_runtime::core::engine_state::deploy_item::DeployItem; use types::account::AccountHash; use crate::engine_server::{ipc, mappings::MappingError}; diff --git a/grpc/server/src/engine_server/mappings/ipc/deploy_result.rs b/grpc/server/src/engine_server/mappings/ipc/deploy_result.rs index b7262f055d..7e133420cf 100644 --- a/grpc/server/src/engine_server/mappings/ipc/deploy_result.rs +++ b/grpc/server/src/engine_server/mappings/ipc/deploy_result.rs @@ -1,11 +1,11 @@ -use node::contract_core::{ +use node::components::contract_runtime::core::{ engine_state::{ execution_effect::ExecutionEffect, execution_result::ExecutionResult, Error as EngineStateError, }, execution::Error as ExecutionError, }; -use node::contract_shared::gas::Gas; +use node::components::contract_runtime::shared::gas::Gas; use crate::engine_server::ipc::{DeployError_OutOfGasError, DeployResult}; @@ -146,7 +146,9 @@ mod detail { mod tests { use std::convert::TryInto; - use node::contract_shared::{additive_map::AdditiveMap, transform::Transform}; + use node::components::contract_runtime::shared::{ + additive_map::AdditiveMap, transform::Transform, + }; use types::{bytesrepr::Error as BytesReprError, AccessRights, ApiError, Key, URef, U512}; use super::*; diff --git a/grpc/server/src/engine_server/mappings/ipc/exec_config.rs b/grpc/server/src/engine_server/mappings/ipc/exec_config.rs index df5ae3437f..4648da19e1 100644 --- a/grpc/server/src/engine_server/mappings/ipc/exec_config.rs +++ b/grpc/server/src/engine_server/mappings/ipc/exec_config.rs @@ -1,6 +1,6 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_core::engine_state::genesis::{ExecConfig, GenesisAccount}; +use node::components::contract_runtime::core::engine_state::genesis::{ExecConfig, GenesisAccount}; use crate::engine_server::{ipc, mappings::MappingError}; diff --git a/grpc/server/src/engine_server/mappings/ipc/executable_deploy_item.rs b/grpc/server/src/engine_server/mappings/ipc/executable_deploy_item.rs index 4e0a8e6c7c..e49e30865c 100644 --- a/grpc/server/src/engine_server/mappings/ipc/executable_deploy_item.rs +++ b/grpc/server/src/engine_server/mappings/ipc/executable_deploy_item.rs @@ -1,6 +1,6 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_core::engine_state::executable_deploy_item::ExecutableDeployItem; +use node::components::contract_runtime::core::engine_state::executable_deploy_item::ExecutableDeployItem; use crate::engine_server::{ ipc::{DeployPayload, DeployPayload_oneof_payload}, diff --git a/grpc/server/src/engine_server/mappings/ipc/execute_request.rs b/grpc/server/src/engine_server/mappings/ipc/execute_request.rs index 160d474724..6a273b8d5c 100644 --- a/grpc/server/src/engine_server/mappings/ipc/execute_request.rs +++ b/grpc/server/src/engine_server/mappings/ipc/execute_request.rs @@ -1,9 +1,9 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_core::engine_state::{ +use node::components::contract_runtime::core::engine_state::{ execute_request::ExecuteRequest, execution_result::ExecutionResult, }; -use node::contract_shared::newtypes::BLAKE2B_DIGEST_LENGTH; +use node::components::contract_runtime::shared::newtypes::BLAKE2B_DIGEST_LENGTH; use crate::engine_server::{ipc, mappings::MappingError}; diff --git a/grpc/server/src/engine_server/mappings/ipc/execution_effect.rs b/grpc/server/src/engine_server/mappings/ipc/execution_effect.rs index eaa5217360..7e0ae68104 100644 --- a/grpc/server/src/engine_server/mappings/ipc/execution_effect.rs +++ b/grpc/server/src/engine_server/mappings/ipc/execution_effect.rs @@ -1,4 +1,6 @@ -use node::contract_core::engine_state::{execution_effect::ExecutionEffect, op::Op}; +use node::components::contract_runtime::core::engine_state::{ + execution_effect::ExecutionEffect, op::Op, +}; use types::Key; use crate::engine_server::{ diff --git a/grpc/server/src/engine_server/mappings/ipc/genesis_account.rs b/grpc/server/src/engine_server/mappings/ipc/genesis_account.rs index e5ecfbc393..b33be2b4f9 100644 --- a/grpc/server/src/engine_server/mappings/ipc/genesis_account.rs +++ b/grpc/server/src/engine_server/mappings/ipc/genesis_account.rs @@ -1,7 +1,7 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_core::engine_state::genesis::GenesisAccount; -use node::contract_shared::motes::Motes; +use node::components::contract_runtime::core::engine_state::genesis::GenesisAccount; +use node::components::contract_runtime::shared::motes::Motes; use types::account::AccountHash; use crate::engine_server::{ diff --git a/grpc/server/src/engine_server/mappings/ipc/genesis_config.rs b/grpc/server/src/engine_server/mappings/ipc/genesis_config.rs index 3bbd5343e2..1536fad979 100644 --- a/grpc/server/src/engine_server/mappings/ipc/genesis_config.rs +++ b/grpc/server/src/engine_server/mappings/ipc/genesis_config.rs @@ -1,6 +1,6 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_core::engine_state::genesis::GenesisConfig; +use node::components::contract_runtime::core::engine_state::genesis::GenesisConfig; use crate::engine_server::{ipc::ChainSpec_GenesisConfig, mappings::MappingError}; diff --git a/grpc/server/src/engine_server/mappings/ipc/query_request.rs b/grpc/server/src/engine_server/mappings/ipc/query_request.rs index 2989865f5c..8bbc460da7 100644 --- a/grpc/server/src/engine_server/mappings/ipc/query_request.rs +++ b/grpc/server/src/engine_server/mappings/ipc/query_request.rs @@ -1,7 +1,7 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_core::engine_state::query::QueryRequest; -use node::contract_shared::newtypes::BLAKE2B_DIGEST_LENGTH; +use node::components::contract_runtime::core::engine_state::query::QueryRequest; +use node::components::contract_runtime::shared::newtypes::BLAKE2B_DIGEST_LENGTH; use crate::engine_server::{ipc, mappings::MappingError}; diff --git a/grpc/server/src/engine_server/mappings/ipc/run_genesis_request.rs b/grpc/server/src/engine_server/mappings/ipc/run_genesis_request.rs index 5663746ae1..30d8e4403e 100644 --- a/grpc/server/src/engine_server/mappings/ipc/run_genesis_request.rs +++ b/grpc/server/src/engine_server/mappings/ipc/run_genesis_request.rs @@ -1,6 +1,6 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_core::engine_state::run_genesis_request::RunGenesisRequest; +use node::components::contract_runtime::core::engine_state::run_genesis_request::RunGenesisRequest; use crate::engine_server::{ipc, mappings::MappingError}; diff --git a/grpc/server/src/engine_server/mappings/ipc/upgrade_request.rs b/grpc/server/src/engine_server/mappings/ipc/upgrade_request.rs index e269498a43..0910f44973 100644 --- a/grpc/server/src/engine_server/mappings/ipc/upgrade_request.rs +++ b/grpc/server/src/engine_server/mappings/ipc/upgrade_request.rs @@ -1,6 +1,6 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_core::engine_state::upgrade::UpgradeConfig; +use node::components::contract_runtime::core::engine_state::upgrade::UpgradeConfig; use types::ProtocolVersion; use crate::engine_server::{ipc::UpgradeRequest, mappings::MappingError}; diff --git a/grpc/server/src/engine_server/mappings/ipc/wasm_costs.rs b/grpc/server/src/engine_server/mappings/ipc/wasm_costs.rs index 33c3e6c067..588701db98 100644 --- a/grpc/server/src/engine_server/mappings/ipc/wasm_costs.rs +++ b/grpc/server/src/engine_server/mappings/ipc/wasm_costs.rs @@ -1,4 +1,4 @@ -use node::contract_shared::wasm_costs::WasmCosts; +use node::components::contract_runtime::shared::wasm_costs::WasmCosts; use crate::engine_server::ipc::ChainSpec_CostTable_WasmCosts; @@ -41,7 +41,7 @@ impl From for WasmCosts { mod tests { use proptest::proptest; - use node::contract_shared::wasm_costs::gens; + use node::components::contract_runtime::shared::wasm_costs::gens; use super::*; use crate::engine_server::mappings::test_utils; diff --git a/grpc/server/src/engine_server/mappings/mod.rs b/grpc/server/src/engine_server/mappings/mod.rs index 64a2cec590..dab645e52b 100644 --- a/grpc/server/src/engine_server/mappings/mod.rs +++ b/grpc/server/src/engine_server/mappings/mod.rs @@ -10,7 +10,7 @@ use std::{ string::ToString, }; -use node::contract_core::{engine_state, DEPLOY_HASH_LENGTH}; +use node::components::contract_runtime::core::{engine_state, DEPLOY_HASH_LENGTH}; use types::{account::ACCOUNT_HASH_LENGTH, KEY_HASH_LENGTH}; pub use transforms::TransformMap; diff --git a/grpc/server/src/engine_server/mappings/state/account.rs b/grpc/server/src/engine_server/mappings/state/account.rs index 896f035578..cdab1f3e08 100644 --- a/grpc/server/src/engine_server/mappings/state/account.rs +++ b/grpc/server/src/engine_server/mappings/state/account.rs @@ -4,7 +4,9 @@ use std::{ mem, }; -use node::contract_shared::account::{Account, ActionThresholds, AssociatedKeys}; +use node::components::contract_runtime::shared::account::{ + Account, ActionThresholds, AssociatedKeys, +}; use types::account::{AccountHash, Weight}; use super::NamedKeyMap; @@ -140,7 +142,7 @@ fn weight_from(value: u32, value_name: &str) -> Result { mod tests { use proptest::proptest; - use node::contract_shared::account::gens; + use node::components::contract_runtime::shared::account::gens; use super::*; use crate::engine_server::mappings::test_utils; diff --git a/grpc/server/src/engine_server/mappings/state/stored_value.rs b/grpc/server/src/engine_server/mappings/state/stored_value.rs index 0a1d95d080..7660bbe33b 100644 --- a/grpc/server/src/engine_server/mappings/state/stored_value.rs +++ b/grpc/server/src/engine_server/mappings/state/stored_value.rs @@ -1,4 +1,4 @@ -use node::contract_shared::stored_value::StoredValue; +use node::components::contract_runtime::shared::stored_value::StoredValue; use crate::engine_server::{ mappings::ParsingError, @@ -62,7 +62,7 @@ impl TryFrom for StoredValue { mod tests { use proptest::proptest; - use node::contract_shared::stored_value::gens; + use node::components::contract_runtime::shared::stored_value::gens; use super::*; use crate::engine_server::mappings::test_utils; diff --git a/grpc/server/src/engine_server/mappings/transforms/error.rs b/grpc/server/src/engine_server/mappings/transforms/error.rs index 6c24703433..cb442a8805 100644 --- a/grpc/server/src/engine_server/mappings/transforms/error.rs +++ b/grpc/server/src/engine_server/mappings/transforms/error.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; -use node::contract_shared::{transform, TypeMismatch}; +use node::components::contract_runtime::shared::{transform, TypeMismatch}; use crate::engine_server::{ mappings::ParsingError, diff --git a/grpc/server/src/engine_server/mappings/transforms/transform.rs b/grpc/server/src/engine_server/mappings/transforms/transform.rs index 8770ab8a92..0ee31f9e24 100644 --- a/grpc/server/src/engine_server/mappings/transforms/transform.rs +++ b/grpc/server/src/engine_server/mappings/transforms/transform.rs @@ -1,6 +1,6 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_shared::{ +use node::components::contract_runtime::shared::{ stored_value::StoredValue, transform::{Error as TransformError, Transform}, }; @@ -108,7 +108,7 @@ impl TryFrom for Transform { mod tests { use proptest::proptest; - use node::contract_shared::transform::gens; + use node::components::contract_runtime::shared::transform::gens; use super::*; use crate::engine_server::mappings::test_utils; diff --git a/grpc/server/src/engine_server/mappings/transforms/transform_entry.rs b/grpc/server/src/engine_server/mappings/transforms/transform_entry.rs index 693777ba38..70f4bf0c22 100644 --- a/grpc/server/src/engine_server/mappings/transforms/transform_entry.rs +++ b/grpc/server/src/engine_server/mappings/transforms/transform_entry.rs @@ -1,6 +1,6 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_shared::transform::Transform; +use node::components::contract_runtime::shared::transform::Transform; use types::Key; use crate::engine_server::{mappings::ParsingError, transforms::TransformEntry}; @@ -38,7 +38,7 @@ impl TryFrom for (Key, Transform) { mod tests { use proptest::proptest; - use node::contract_shared::transform; + use node::components::contract_runtime::shared::transform; use super::*; use crate::engine_server::mappings::test_utils; diff --git a/grpc/server/src/engine_server/mappings/transforms/transform_map.rs b/grpc/server/src/engine_server/mappings/transforms/transform_map.rs index 61bdcd455c..217524815f 100644 --- a/grpc/server/src/engine_server/mappings/transforms/transform_map.rs +++ b/grpc/server/src/engine_server/mappings/transforms/transform_map.rs @@ -1,6 +1,6 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_shared::{additive_map::AdditiveMap, transform::Transform}; +use node::components::contract_runtime::shared::{additive_map::AdditiveMap, transform::Transform}; use types::Key; use crate::engine_server::{mappings::ParsingError, transforms::TransformEntry}; @@ -28,7 +28,7 @@ impl TryFrom> for TransformMap { #[cfg(test)] mod tests { - use node::contract_shared::stored_value::StoredValue; + use node::components::contract_runtime::shared::stored_value::StoredValue; use types::CLValue; use super::*; diff --git a/grpc/server/src/engine_server/mod.rs b/grpc/server/src/engine_server/mod.rs index 8a35b03b24..bb016aeb01 100644 --- a/grpc/server/src/engine_server/mod.rs +++ b/grpc/server/src/engine_server/mod.rs @@ -29,7 +29,7 @@ use std::{ use grpc::{Error as GrpcError, RequestOptions, ServerBuilder, SingleResponse}; use log::{info, warn, Level}; -use node::contract_core::{ +use node::components::contract_runtime::core::{ engine_state::{ execute_request::ExecuteRequest, genesis::GenesisResult, @@ -40,11 +40,11 @@ use node::contract_core::{ }, execution, }; -use node::contract_shared::{ +use node::components::contract_runtime::shared::{ logging::{self, log_duration}, newtypes::{Blake2bHash, CorrelationId}, }; -use node::contract_storage::global_state::{CommitResult, StateProvider}; +use node::components::contract_runtime::storage::global_state::{CommitResult, StateProvider}; use types::{bytesrepr::ToBytes, ProtocolVersion}; use self::{ diff --git a/grpc/server/src/main.rs b/grpc/server/src/main.rs index 099705b76e..16e3db6fb0 100644 --- a/grpc/server/src/main.rs +++ b/grpc/server/src/main.rs @@ -14,19 +14,19 @@ use clap::{App, Arg, ArgMatches}; use dirs::home_dir; use lmdb::DatabaseFlags; use log::{error, info, Level, LevelFilter}; -use node::contract_core::engine_state::{EngineConfig, EngineState}; +use node::components::contract_runtime::core::engine_state::{EngineConfig, EngineState}; -use node::contract_shared::{ +use node::components::contract_runtime::shared::{ logging::{self, Settings, Style}, page_size, socket, }; -use node::contract_storage::{ +use node::components::contract_runtime::storage::{ global_state::lmdb::LmdbGlobalState, transaction_source::lmdb::LmdbEnvironment, trie_store::lmdb::LmdbTrieStore, }; use casperlabs_engine_grpc_server::engine_server; -use node::contract_storage::protocol_data_store::lmdb::LmdbProtocolDataStore; +use node::components::contract_runtime::storage::protocol_data_store::lmdb::LmdbProtocolDataStore; // exe / proc const PROC_NAME: &str = "casperlabs-engine-grpc-server"; diff --git a/grpc/test-support/Cargo.toml b/grpc/test_support/Cargo.toml similarity index 95% rename from grpc/test-support/Cargo.toml rename to grpc/test_support/Cargo.toml index ec6ce6d2d9..fd5d36e2f3 100644 --- a/grpc/test-support/Cargo.toml +++ b/grpc/test_support/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/CasperLabs/CasperLabs/tree/master/execution-eng license-file = "../../LICENSE" [dependencies] -contract = { version = "0.6.0", path = "../../smart-contracts/contract", package = "casperlabs-contract" } +contract = { version = "0.6.0", path = "../../smart_contracts/contract", package = "casperlabs-contract" } engine-grpc-server = { version = "0.20.0", path = "../server", package = "casperlabs-engine-grpc-server" } node = { path = "../../node", package = "casperlabs-node" } grpc = "0.6.1" diff --git a/grpc/test-support/README.md b/grpc/test_support/README.md similarity index 100% rename from grpc/test-support/README.md rename to grpc/test_support/README.md diff --git a/grpc/test-support/src/account.rs b/grpc/test_support/src/account.rs similarity index 75% rename from grpc/test-support/src/account.rs rename to grpc/test_support/src/account.rs index 4eaeb51951..ac303bcc52 100644 --- a/grpc/test-support/src/account.rs +++ b/grpc/test_support/src/account.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; -use node::contract_shared; -use node::contract_shared::stored_value::StoredValue; +use node::components::contract_runtime::shared; +use node::components::contract_runtime::shared::stored_value::StoredValue; use types::{account::AccountHash, contracts::NamedKeys, URef}; use crate::{Error, Result}; @@ -9,12 +9,12 @@ use crate::{Error, Result}; /// An `Account` instance. #[derive(Eq, PartialEq, Clone, Debug)] pub struct Account { - inner: contract_shared::account::Account, + inner: shared::account::Account, } impl Account { /// creates a new Account instance. - pub(crate) fn new(account: contract_shared::account::Account) -> Self { + pub(crate) fn new(account: shared::account::Account) -> Self { Account { inner: account } } @@ -34,8 +34,8 @@ impl Account { } } -impl From for Account { - fn from(value: contract_shared::account::Account) -> Self { +impl From for Account { + fn from(value: shared::account::Account) -> Self { Account::new(value) } } diff --git a/grpc/test-support/src/code.rs b/grpc/test_support/src/code.rs similarity index 100% rename from grpc/test-support/src/code.rs rename to grpc/test_support/src/code.rs diff --git a/grpc/test-support/src/error.rs b/grpc/test_support/src/error.rs similarity index 92% rename from grpc/test-support/src/error.rs rename to grpc/test_support/src/error.rs index 7b582a1083..2fe201a3d6 100644 --- a/grpc/test-support/src/error.rs +++ b/grpc/test_support/src/error.rs @@ -1,6 +1,6 @@ use std::result; -use node::contract_shared::TypeMismatch; +use node::components::contract_runtime::shared::TypeMismatch; use types::CLValueError; /// The error type returned by any casperlabs-engine-test-support operation. diff --git a/grpc/test-support/src/internal/additive_map_diff.rs b/grpc/test_support/src/internal/additive_map_diff.rs similarity index 98% rename from grpc/test-support/src/internal/additive_map_diff.rs rename to grpc/test_support/src/internal/additive_map_diff.rs index c3d0e598d1..06fc927ab5 100644 --- a/grpc/test-support/src/internal/additive_map_diff.rs +++ b/grpc/test_support/src/internal/additive_map_diff.rs @@ -1,4 +1,4 @@ -use node::contract_shared::{additive_map::AdditiveMap, transform::Transform}; +use node::components::contract_runtime::shared::{additive_map::AdditiveMap, transform::Transform}; use types::Key; /// Represents the difference between two `AdditiveMap`s. diff --git a/grpc/test-support/src/internal/deploy_item_builder.rs b/grpc/test_support/src/internal/deploy_item_builder.rs similarity index 99% rename from grpc/test-support/src/internal/deploy_item_builder.rs rename to grpc/test_support/src/internal/deploy_item_builder.rs index dad1d78970..79c31eac0f 100644 --- a/grpc/test-support/src/internal/deploy_item_builder.rs +++ b/grpc/test_support/src/internal/deploy_item_builder.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeSet, path::Path}; -use node::contract_core::{ +use node::components::contract_runtime::core::{ engine_state::{deploy_item::DeployItem, executable_deploy_item::ExecutableDeployItem}, DeployHash, }; diff --git a/grpc/test-support/src/internal/exec_with_return.rs b/grpc/test_support/src/internal/exec_with_return.rs similarity index 94% rename from grpc/test-support/src/internal/exec_with_return.rs rename to grpc/test_support/src/internal/exec_with_return.rs index a3990f3ea2..6dc8ad9879 100644 --- a/grpc/test-support/src/internal/exec_with_return.rs +++ b/grpc/test_support/src/internal/exec_with_return.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, collections::BTreeSet, convert::TryInto, rc::Rc}; use engine_grpc_server::engine_server::ipc_grpc::ExecutionEngineService; -use node::contract_core::{ +use node::components::contract_runtime::core::{ engine_state::{ executable_deploy_item::ExecutableDeployItem, execution_effect::ExecutionEffect, EngineConfig, EngineState, @@ -10,9 +10,11 @@ use node::contract_core::{ runtime::{self, Runtime}, runtime_context::RuntimeContext, }; -use node::contract_shared::wasm_prep::Preprocessor; -use node::contract_shared::{gas::Gas, newtypes::CorrelationId}; -use node::contract_storage::{global_state::StateProvider, protocol_data::ProtocolData}; +use node::components::contract_runtime::shared::wasm_prep::Preprocessor; +use node::components::contract_runtime::shared::{gas::Gas, newtypes::CorrelationId}; +use node::components::contract_runtime::storage::{ + global_state::StateProvider, protocol_data::ProtocolData, +}; use types::{ account::AccountHash, bytesrepr::FromBytes, BlockTime, CLTyped, EntryPointType, Key, Phase, ProtocolVersion, RuntimeArgs, URef, U512, diff --git a/grpc/test-support/src/internal/execute_request_builder.rs b/grpc/test_support/src/internal/execute_request_builder.rs similarity index 96% rename from grpc/test-support/src/internal/execute_request_builder.rs rename to grpc/test_support/src/internal/execute_request_builder.rs index dbfd0003e1..cae8a453aa 100644 --- a/grpc/test-support/src/internal/execute_request_builder.rs +++ b/grpc/test_support/src/internal/execute_request_builder.rs @@ -2,7 +2,9 @@ use std::convert::TryInto; use rand::Rng; -use node::contract_core::engine_state::{deploy_item::DeployItem, execute_request::ExecuteRequest}; +use node::components::contract_runtime::core::engine_state::{ + deploy_item::DeployItem, execute_request::ExecuteRequest, +}; use types::{ account::AccountHash, contracts::ContractVersion, runtime_args, ContractHash, ProtocolVersion, RuntimeArgs, diff --git a/grpc/test-support/src/internal/mod.rs b/grpc/test_support/src/internal/mod.rs similarity index 93% rename from grpc/test-support/src/internal/mod.rs rename to grpc/test_support/src/internal/mod.rs index fc92d6cd5c..75e14d7936 100644 --- a/grpc/test-support/src/internal/mod.rs +++ b/grpc/test_support/src/internal/mod.rs @@ -9,12 +9,12 @@ mod wasm_test_builder; use lazy_static::lazy_static; use num_traits::identities::Zero; -use node::contract_core::engine_state::{ +use node::components::contract_runtime::core::engine_state::{ genesis::{ExecConfig, GenesisAccount, GenesisConfig}, run_genesis_request::RunGenesisRequest, }; -use node::contract_shared::wasm_costs::WasmCosts; -use node::contract_shared::{motes::Motes, newtypes::Blake2bHash, test_utils}; +use node::components::contract_runtime::shared::wasm_costs::WasmCosts; +use node::components::contract_runtime::shared::{motes::Motes, newtypes::Blake2bHash, test_utils}; use types::{account::AccountHash, ProtocolVersion, U512}; use super::{DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE}; diff --git a/grpc/test-support/src/internal/upgrade_request_builder.rs b/grpc/test_support/src/internal/upgrade_request_builder.rs similarity index 98% rename from grpc/test-support/src/internal/upgrade_request_builder.rs rename to grpc/test_support/src/internal/upgrade_request_builder.rs index 7fab2ea12d..fc188a2211 100644 --- a/grpc/test-support/src/internal/upgrade_request_builder.rs +++ b/grpc/test_support/src/internal/upgrade_request_builder.rs @@ -5,7 +5,7 @@ use engine_grpc_server::engine_server::{ }, state, }; -use node::contract_shared::wasm_costs::WasmCosts; +use node::components::contract_runtime::shared::wasm_costs::WasmCosts; use types::ProtocolVersion; pub struct UpgradeRequestBuilder { diff --git a/grpc/test-support/src/internal/utils.rs b/grpc/test_support/src/internal/utils.rs similarity index 98% rename from grpc/test-support/src/internal/utils.rs rename to grpc/test_support/src/internal/utils.rs index 59b7cbbf0c..c28f625c75 100644 --- a/grpc/test-support/src/internal/utils.rs +++ b/grpc/test_support/src/internal/utils.rs @@ -6,13 +6,13 @@ use std::{ use lazy_static::lazy_static; -use node::contract_core::engine_state::{ +use node::components::contract_runtime::core::engine_state::{ execution_result::ExecutionResult, genesis::{ExecConfig, GenesisAccount, GenesisConfig}, run_genesis_request::RunGenesisRequest, Error, }; -use node::contract_shared::{ +use node::components::contract_runtime::shared::{ account::Account, additive_map::AdditiveMap, gas::Gas, stored_value::StoredValue, transform::Transform, }; @@ -62,7 +62,7 @@ lazy_static! { // repo, i.e. 'CasperLabs/execution-engine/target/wasm32-unknown-unknown/release/'. static ref ASSEMBLY_SCRIPT_WORKSPACE_WASM_PATH: PathBuf = { let path = RUST_WORKSPACE_PATH - .join("target-as"); + .join("target_as"); assert!(path.exists(), "AssemblyScript WASM path {} does not exist.", path.display()); path diff --git a/grpc/test-support/src/internal/wasm_test_builder.rs b/grpc/test_support/src/internal/wasm_test_builder.rs similarity index 99% rename from grpc/test-support/src/internal/wasm_test_builder.rs rename to grpc/test_support/src/internal/wasm_test_builder.rs index 665adb1906..6ad6f426b6 100644 --- a/grpc/test-support/src/internal/wasm_test_builder.rs +++ b/grpc/test_support/src/internal/wasm_test_builder.rs @@ -21,14 +21,14 @@ use engine_grpc_server::engine_server::{ mappings::{MappingError, TransformMap}, transforms::TransformEntry, }; -use node::contract_core::{ +use node::components::contract_runtime::core::{ engine_state::{ execute_request::ExecuteRequest, execution_result::ExecutionResult, run_genesis_request::RunGenesisRequest, EngineConfig, EngineState, SYSTEM_ACCOUNT_ADDR, }, execution, }; -use node::contract_shared::{ +use node::components::contract_runtime::shared::{ account::Account, additive_map::AdditiveMap, gas::Gas, @@ -38,7 +38,7 @@ use node::contract_shared::{ stored_value::StoredValue, transform::Transform, }; -use node::contract_storage::{ +use node::components::contract_runtime::storage::{ global_state::{in_memory::InMemoryGlobalState, lmdb::LmdbGlobalState, StateProvider}, protocol_data_store::lmdb::LmdbProtocolDataStore, transaction_source::lmdb::LmdbEnvironment, diff --git a/grpc/test-support/src/lib.rs b/grpc/test_support/src/lib.rs similarity index 100% rename from grpc/test-support/src/lib.rs rename to grpc/test_support/src/lib.rs diff --git a/grpc/test-support/src/session.rs b/grpc/test_support/src/session.rs similarity index 98% rename from grpc/test-support/src/session.rs rename to grpc/test_support/src/session.rs index 468e96075b..95011c2524 100644 --- a/grpc/test-support/src/session.rs +++ b/grpc/test_support/src/session.rs @@ -1,6 +1,6 @@ use rand::Rng; -use node::contract_core::engine_state::execute_request::ExecuteRequest; +use node::components::contract_runtime::core::engine_state::execute_request::ExecuteRequest; use types::{runtime_args, ProtocolVersion, RuntimeArgs, URef, U512}; use crate::{ diff --git a/grpc/test-support/src/test_context.rs b/grpc/test_support/src/test_context.rs similarity index 98% rename from grpc/test-support/src/test_context.rs rename to grpc/test_support/src/test_context.rs index 3609e9f44b..98b4851e6c 100644 --- a/grpc/test-support/src/test_context.rs +++ b/grpc/test_support/src/test_context.rs @@ -1,12 +1,12 @@ use num_traits::identities::Zero; -use node::contract_core::engine_state::{ +use node::components::contract_runtime::core::engine_state::{ genesis::{GenesisAccount, GenesisConfig}, run_genesis_request::RunGenesisRequest, CONV_RATE, }; -use node::contract_shared::motes::Motes; +use node::components::contract_runtime::shared::motes::Motes; use types::{AccessRights, Key, URef, U512}; use crate::{ diff --git a/grpc/test-support/src/value.rs b/grpc/test_support/src/value.rs similarity index 93% rename from grpc/test-support/src/value.rs rename to grpc/test_support/src/value.rs index 877b936f89..73df50f9af 100644 --- a/grpc/test-support/src/value.rs +++ b/grpc/test_support/src/value.rs @@ -1,6 +1,6 @@ use std::convert::{TryFrom, TryInto}; -use node::contract_shared::stored_value::StoredValue; +use node::components::contract_runtime::shared::stored_value::StoredValue; use types::{ bytesrepr::{FromBytes, ToBytes}, CLTyped, CLValue, diff --git a/grpc/test-support/tests/version_numbers.rs b/grpc/test_support/tests/version_numbers.rs similarity index 100% rename from grpc/test-support/tests/version_numbers.rs rename to grpc/test_support/tests/version_numbers.rs diff --git a/grpc/tests/Cargo.toml b/grpc/tests/Cargo.toml index db30ab0c14..90137ada1f 100644 --- a/grpc/tests/Cargo.toml +++ b/grpc/tests/Cargo.toml @@ -7,11 +7,11 @@ edition = "2018" [dependencies] base16 = "0.2.1" clap = "2" -contract = { path = "../../smart-contracts/contract", package = "casperlabs-contract" } +contract = { path = "../../smart_contracts/contract", package = "casperlabs-contract" } crossbeam-channel = "0.4.0" node = { path = "../../node", package = "casperlabs-node" } engine-grpc-server = { path = "../server", package = "casperlabs-engine-grpc-server" } -engine-test-support = { path = "../test-support", package = "casperlabs-engine-test-support" } +engine-test-support = { path = "../test_support", package = "casperlabs-engine-test-support" } env_logger = "0.7.1" grpc = "0.6.1" log = "0.4.8" diff --git a/grpc/tests/benches/transfer_bench.rs b/grpc/tests/benches/transfer_bench.rs index 97f33786c5..28a0c302ff 100644 --- a/grpc/tests/benches/transfer_bench.rs +++ b/grpc/tests/benches/transfer_bench.rs @@ -12,7 +12,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::EngineConfig; +use node::components::contract_runtime::core::engine_state::EngineConfig; use types::{account::AccountHash, runtime_args, Key, RuntimeArgs, URef, U512}; const CONTRACT_CREATE_ACCOUNTS: &str = "create_accounts.wasm"; diff --git a/grpc/tests/src/logging/metrics.rs b/grpc/tests/src/logging/metrics.rs index bbad57037d..40a5775740 100644 --- a/grpc/tests/src/logging/metrics.rs +++ b/grpc/tests/src/logging/metrics.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex}; use log::{Metadata, Record}; -use node::contract_shared::logging::TerminalLogger; +use node::components::contract_runtime::shared::logging::TerminalLogger; struct Logger { terminal_logger: TerminalLogger, diff --git a/grpc/tests/src/profiling/host_function_metrics.rs b/grpc/tests/src/profiling/host_function_metrics.rs index 5c75c445ce..4474379a0a 100644 --- a/grpc/tests/src/profiling/host_function_metrics.rs +++ b/grpc/tests/src/profiling/host_function_metrics.rs @@ -22,8 +22,8 @@ use serde_json::Value; use engine_test_support::internal::{ DeployItemBuilder, ExecuteRequestBuilder, LmdbWasmTestBuilder, }; -use node::contract_core::engine_state::EngineConfig; -use node::contract_shared::logging::{self, Settings}; +use node::components::contract_runtime::core::engine_state::EngineConfig; +use node::components::contract_runtime::shared::logging::{self, Settings}; use types::{runtime_args, ApiError, RuntimeArgs}; use casperlabs_engine_tests::profiling; diff --git a/grpc/tests/src/profiling/simple_transfer.rs b/grpc/tests/src/profiling/simple_transfer.rs index c281bf310e..1a50da146d 100644 --- a/grpc/tests/src/profiling/simple_transfer.rs +++ b/grpc/tests/src/profiling/simple_transfer.rs @@ -14,7 +14,7 @@ use clap::{crate_version, App, Arg}; use engine_test_support::internal::{ DeployItemBuilder, ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_PAYMENT, }; -use node::contract_core::engine_state::EngineConfig; +use node::components::contract_runtime::core::engine_state::EngineConfig; use types::{runtime_args, RuntimeArgs, U512}; use casperlabs_engine_tests::profiling; diff --git a/grpc/tests/src/profiling/state_initializer.rs b/grpc/tests/src/profiling/state_initializer.rs index 6353c29c43..4be0cf13ee 100644 --- a/grpc/tests/src/profiling/state_initializer.rs +++ b/grpc/tests/src/profiling/state_initializer.rs @@ -15,7 +15,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::{ +use node::components::contract_runtime::core::engine_state::{ engine_config::EngineConfig, genesis::ExecConfig, run_genesis_request::RunGenesisRequest, }; diff --git a/grpc/tests/src/test/contract_api/account/authorized_keys.rs b/grpc/tests/src/test/contract_api/account/authorized_keys.rs index 32fe33deda..6c69b95a67 100644 --- a/grpc/tests/src/test/contract_api/account/authorized_keys.rs +++ b/grpc/tests/src/test/contract_api/account/authorized_keys.rs @@ -5,7 +5,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::{engine_state, execution}; +use node::components::contract_runtime::core::{engine_state, execution}; use types::{ account::{AccountHash, Weight}, runtime_args, RuntimeArgs, diff --git a/grpc/tests/src/test/contract_api/create_purse.rs b/grpc/tests/src/test/contract_api/create_purse.rs index 211e353d5e..bd3f7dc918 100644 --- a/grpc/tests/src/test/contract_api/create_purse.rs +++ b/grpc/tests/src/test/contract_api/create_purse.rs @@ -6,7 +6,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_shared::transform::Transform; +use node::components::contract_runtime::shared::transform::Transform; use types::{account::AccountHash, runtime_args, Key, RuntimeArgs, U512}; const CONTRACT_CREATE_PURSE_01: &str = "create_purse_01.wasm"; diff --git a/grpc/tests/src/test/contract_api/main_purse.rs b/grpc/tests/src/test/contract_api/main_purse.rs index 0fdd10991f..52132fc07f 100644 --- a/grpc/tests/src/test/contract_api/main_purse.rs +++ b/grpc/tests/src/test/contract_api/main_purse.rs @@ -5,7 +5,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_shared::stored_value::StoredValue; +use node::components::contract_runtime::shared::stored_value::StoredValue; use types::{account::AccountHash, runtime_args, Key, RuntimeArgs}; const CONTRACT_MAIN_PURSE: &str = "main_purse.wasm"; diff --git a/grpc/tests/src/test/contract_api/subcall.rs b/grpc/tests/src/test/contract_api/subcall.rs index 175e03c54c..36ab850f40 100644 --- a/grpc/tests/src/test/contract_api/subcall.rs +++ b/grpc/tests/src/test/contract_api/subcall.rs @@ -7,7 +7,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::CONV_RATE; +use node::components::contract_runtime::core::engine_state::CONV_RATE; use types::{contracts::CONTRACT_INITIAL_VERSION, runtime_args, RuntimeArgs, U512}; const ARG_TARGET: &str = "target_contract"; diff --git a/grpc/tests/src/test/contract_api/transfer.rs b/grpc/tests/src/test/contract_api/transfer.rs index 5bf2256a7a..b334b2e3a6 100644 --- a/grpc/tests/src/test/contract_api/transfer.rs +++ b/grpc/tests/src/test/contract_api/transfer.rs @@ -7,8 +7,8 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, }; -use node::contract_core::engine_state::CONV_RATE; -use node::contract_shared::motes::Motes; +use node::components::contract_runtime::core::engine_state::CONV_RATE; +use node::components::contract_runtime::shared::motes::Motes; use types::{account::AccountHash, runtime_args, ApiError, RuntimeArgs, U512}; const CONTRACT_TRANSFER_PURSE_TO_ACCOUNT: &str = "transfer_purse_to_account.wasm"; diff --git a/grpc/tests/src/test/contract_api/transfer_purse_to_account.rs b/grpc/tests/src/test/contract_api/transfer_purse_to_account.rs index 001be34468..728e76e1d9 100644 --- a/grpc/tests/src/test/contract_api/transfer_purse_to_account.rs +++ b/grpc/tests/src/test/contract_api/transfer_purse_to_account.rs @@ -9,7 +9,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, }; -use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use node::components::contract_runtime::shared::{stored_value::StoredValue, transform::Transform}; use types::{ account::AccountHash, runtime_args, ApiError, CLValue, Key, RuntimeArgs, TransferResult, TransferredTo, U512, diff --git a/grpc/tests/src/test/contract_api/transfer_stored.rs b/grpc/tests/src/test/contract_api/transfer_stored.rs index 9f8bfdaecf..0ebe991197 100644 --- a/grpc/tests/src/test/contract_api/transfer_stored.rs +++ b/grpc/tests/src/test/contract_api/transfer_stored.rs @@ -5,8 +5,8 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, }; -use node::contract_core::engine_state::CONV_RATE; -use node::contract_shared::motes::Motes; +use node::components::contract_runtime::core::engine_state::CONV_RATE; +use node::components::contract_runtime::shared::motes::Motes; use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; const CONTRACT_TRANSFER_TO_ACCOUNT_NAME: &str = "transfer_to_account"; diff --git a/grpc/tests/src/test/contract_api/transfer_u512_stored.rs b/grpc/tests/src/test/contract_api/transfer_u512_stored.rs index 70c433a4e6..7b557ac66a 100644 --- a/grpc/tests/src/test/contract_api/transfer_u512_stored.rs +++ b/grpc/tests/src/test/contract_api/transfer_u512_stored.rs @@ -5,8 +5,8 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, }; -use node::contract_core::engine_state::CONV_RATE; -use node::contract_shared::motes::Motes; +use node::components::contract_runtime::core::engine_state::CONV_RATE; +use node::components::contract_runtime::shared::motes::Motes; use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; const FUNCTION_NAME: &str = "transfer"; diff --git a/grpc/tests/src/test/contract_context.rs b/grpc/tests/src/test/contract_context.rs index 168e6e7b11..9392ef1f4a 100644 --- a/grpc/tests/src/test/contract_context.rs +++ b/grpc/tests/src/test/contract_context.rs @@ -6,7 +6,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::{engine_state::Error, execution}; +use node::components::contract_runtime::core::{engine_state::Error, execution}; use types::{contracts::CONTRACT_INITIAL_VERSION, runtime_args, Key, RuntimeArgs}; const CONTRACT_HEADERS: &str = "contract_context.wasm"; diff --git a/grpc/tests/src/test/deploy/non_standard_payment.rs b/grpc/tests/src/test/deploy/non_standard_payment.rs index 1e2647ce13..3f22e51686 100644 --- a/grpc/tests/src/test/deploy/non_standard_payment.rs +++ b/grpc/tests/src/test/deploy/non_standard_payment.rs @@ -5,8 +5,8 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::CONV_RATE; -use node::contract_shared::motes::Motes; +use node::components::contract_runtime::core::engine_state::CONV_RATE; +use node::components::contract_runtime::shared::motes::Motes; use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([42u8; 32]); diff --git a/grpc/tests/src/test/deploy/preconditions.rs b/grpc/tests/src/test/deploy/preconditions.rs index 2caf4ce3c2..f451991f24 100644 --- a/grpc/tests/src/test/deploy/preconditions.rs +++ b/grpc/tests/src/test/deploy/preconditions.rs @@ -7,7 +7,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::Error; +use node::components::contract_runtime::core::engine_state::Error; use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([42u8; 32]); diff --git a/grpc/tests/src/test/deploy/stored_contracts.rs b/grpc/tests/src/test/deploy/stored_contracts.rs index cdf32af2e7..7383a31f24 100644 --- a/grpc/tests/src/test/deploy/stored_contracts.rs +++ b/grpc/tests/src/test/deploy/stored_contracts.rs @@ -8,11 +8,11 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, }; -use node::contract_core::engine_state::{upgrade::ActivationPoint, CONV_RATE}; -use node::contract_shared::{ +use node::components::contract_runtime::core::engine_state::{upgrade::ActivationPoint, CONV_RATE}; +use node::components::contract_runtime::shared::{ account::Account, motes::Motes, stored_value::StoredValue, transform::Transform, }; -use node::contract_storage::global_state::in_memory::InMemoryGlobalState; +use node::components::contract_runtime::storage::global_state::in_memory::InMemoryGlobalState; use types::{ account::AccountHash, contracts::{ContractVersion, CONTRACT_INITIAL_VERSION, DEFAULT_ENTRY_POINT_NAME}, diff --git a/grpc/tests/src/test/groups.rs b/grpc/tests/src/test/groups.rs index a35d4aa61a..60017ab31a 100644 --- a/grpc/tests/src/test/groups.rs +++ b/grpc/tests/src/test/groups.rs @@ -8,7 +8,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::{engine_state::Error, execution}; +use node::components::contract_runtime::core::{engine_state::Error, execution}; use types::{ account::AccountHash, contracts::CONTRACT_INITIAL_VERSION, runtime_args, Key, RuntimeArgs, U512, }; diff --git a/grpc/tests/src/test/manage_groups.rs b/grpc/tests/src/test/manage_groups.rs index f08975903e..c016c5a2d2 100644 --- a/grpc/tests/src/test/manage_groups.rs +++ b/grpc/tests/src/test/manage_groups.rs @@ -8,7 +8,7 @@ use engine_test_support::{ DEFAULT_ACCOUNT_ADDR, }; use lazy_static::lazy_static; -use node::contract_core::{engine_state::Error, execution}; +use node::components::contract_runtime::core::{engine_state::Error, execution}; use std::{collections::BTreeSet, iter::FromIterator}; use types::{contracts, contracts::MAX_GROUPS, runtime_args, Group, Key, RuntimeArgs}; diff --git a/grpc/tests/src/test/regression/ee_460.rs b/grpc/tests/src/test/regression/ee_460.rs index 75bc6cca52..17823e927b 100644 --- a/grpc/tests/src/test/regression/ee_460.rs +++ b/grpc/tests/src/test/regression/ee_460.rs @@ -2,7 +2,7 @@ use engine_test_support::{ internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_shared::transform::Transform; +use node::components::contract_runtime::shared::transform::Transform; use types::{runtime_args, RuntimeArgs, U512}; const CONTRACT_EE_460_REGRESSION: &str = "ee_460_regression.wasm"; diff --git a/grpc/tests/src/test/regression/ee_470.rs b/grpc/tests/src/test/regression/ee_470.rs index 7ffd692d9f..f577ce0d1f 100644 --- a/grpc/tests/src/test/regression/ee_470.rs +++ b/grpc/tests/src/test/regression/ee_470.rs @@ -2,7 +2,7 @@ use engine_test_support::{ internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_storage::global_state::in_memory::InMemoryGlobalState; +use node::components::contract_runtime::storage::global_state::in_memory::InMemoryGlobalState; use types::RuntimeArgs; const CONTRACT_DO_NOTHING: &str = "do_nothing.wasm"; diff --git a/grpc/tests/src/test/regression/ee_532.rs b/grpc/tests/src/test/regression/ee_532.rs index 0de1c02be1..8a40c46f7b 100644 --- a/grpc/tests/src/test/regression/ee_532.rs +++ b/grpc/tests/src/test/regression/ee_532.rs @@ -1,7 +1,7 @@ use engine_test_support::internal::{ ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST, }; -use node::contract_core::engine_state::Error; +use node::components::contract_runtime::core::engine_state::Error; use types::{account::AccountHash, RuntimeArgs}; const CONTRACT_EE_532_REGRESSION: &str = "ee_532_regression.wasm"; diff --git a/grpc/tests/src/test/regression/ee_572.rs b/grpc/tests/src/test/regression/ee_572.rs index 2da285aae7..dc07a17580 100644 --- a/grpc/tests/src/test/regression/ee_572.rs +++ b/grpc/tests/src/test/regression/ee_572.rs @@ -5,7 +5,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_shared::stored_value::StoredValue; +use node::components::contract_runtime::shared::stored_value::StoredValue; use types::{account::AccountHash, runtime_args, Key, RuntimeArgs, U512}; const CONTRACT_CREATE: &str = "ee_572_regression_create.wasm"; diff --git a/grpc/tests/src/test/regression/ee_584.rs b/grpc/tests/src/test/regression/ee_584.rs index 1aa823d78e..b9ae2293c4 100644 --- a/grpc/tests/src/test/regression/ee_584.rs +++ b/grpc/tests/src/test/regression/ee_584.rs @@ -2,7 +2,7 @@ use engine_test_support::{ internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use node::components::contract_runtime::shared::{stored_value::StoredValue, transform::Transform}; use types::RuntimeArgs; const CONTRACT_EE_584_REGRESSION: &str = "ee_584_regression.wasm"; diff --git a/grpc/tests/src/test/regression/ee_598.rs b/grpc/tests/src/test/regression/ee_598.rs index 3a120ffc20..66ce27b5e9 100644 --- a/grpc/tests/src/test/regression/ee_598.rs +++ b/grpc/tests/src/test/regression/ee_598.rs @@ -7,8 +7,8 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::genesis::GenesisAccount; -use node::contract_shared::motes::Motes; +use node::components::contract_runtime::core::engine_state::genesis::GenesisAccount; +use node::components::contract_runtime::shared::motes::Motes; use types::{account::AccountHash, runtime_args, ApiError, RuntimeArgs, U512}; const ARG_AMOUNT: &str = "amount"; diff --git a/grpc/tests/src/test/regression/ee_599.rs b/grpc/tests/src/test/regression/ee_599.rs index ed2ed881ad..064bf3b292 100644 --- a/grpc/tests/src/test/regression/ee_599.rs +++ b/grpc/tests/src/test/regression/ee_599.rs @@ -7,8 +7,8 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::CONV_RATE; -use node::contract_shared::motes::Motes; +use node::components::contract_runtime::core::engine_state::CONV_RATE; +use node::components::contract_runtime::shared::motes::Motes; use types::{account::AccountHash, runtime_args, RuntimeArgs, U512}; const CONTRACT_EE_599_REGRESSION: &str = "ee_599_regression.wasm"; diff --git a/grpc/tests/src/test/regression/ee_601.rs b/grpc/tests/src/test/regression/ee_601.rs index c782194baf..c35f4c5990 100644 --- a/grpc/tests/src/test/regression/ee_601.rs +++ b/grpc/tests/src/test/regression/ee_601.rs @@ -5,7 +5,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use node::components::contract_runtime::shared::{stored_value::StoredValue, transform::Transform}; use types::{runtime_args, CLValue, Key, RuntimeArgs}; const ARG_AMOUNT: &str = "amount"; diff --git a/grpc/tests/src/test/regression/ee_803.rs b/grpc/tests/src/test/regression/ee_803.rs index f2df15324f..77b1011022 100644 --- a/grpc/tests/src/test/regression/ee_803.rs +++ b/grpc/tests/src/test/regression/ee_803.rs @@ -4,12 +4,12 @@ use engine_test_support::{ internal::{utils, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNTS}, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::{ +use node::components::contract_runtime::core::engine_state::{ execution_result::ExecutionResult, genesis::{GenesisAccount, POS_REWARDS_PURSE}, CONV_RATE, }; -use node::contract_shared::motes::Motes; +use node::components::contract_runtime::shared::motes::Motes; use types::{account::AccountHash, runtime_args, Key, RuntimeArgs, URef, U512}; const CONTRACT_DO_NOTHING: &str = "do_nothing.wasm"; diff --git a/grpc/tests/src/test/system_contracts/genesis.rs b/grpc/tests/src/test/system_contracts/genesis.rs index e46ca7831b..c86189b0e0 100644 --- a/grpc/tests/src/test/system_contracts/genesis.rs +++ b/grpc/tests/src/test/system_contracts/genesis.rs @@ -2,12 +2,12 @@ use engine_test_support::internal::{ utils, InMemoryWasmTestBuilder, DEFAULT_WASM_COSTS, MINT_INSTALL_CONTRACT, POS_INSTALL_CONTRACT, STANDARD_PAYMENT_INSTALL_CONTRACT, }; -use node::contract_core::engine_state::{ +use node::components::contract_runtime::core::engine_state::{ genesis::{ExecConfig, GenesisAccount}, run_genesis_request::RunGenesisRequest, SYSTEM_ACCOUNT_ADDR, }; -use node::contract_shared::{motes::Motes, stored_value::StoredValue}; +use node::components::contract_runtime::shared::{motes::Motes, stored_value::StoredValue}; use types::{account::AccountHash, ProtocolVersion, U512}; #[cfg(feature = "use-system-contracts")] diff --git a/grpc/tests/src/test/system_contracts/mint_install.rs b/grpc/tests/src/test/system_contracts/mint_install.rs index ee7bb72bb8..a48bdf77d6 100644 --- a/grpc/tests/src/test/system_contracts/mint_install.rs +++ b/grpc/tests/src/test/system_contracts/mint_install.rs @@ -4,8 +4,8 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::EngineConfig; -use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use node::components::contract_runtime::core::engine_state::EngineConfig; +use node::components::contract_runtime::shared::{stored_value::StoredValue, transform::Transform}; use types::{ contracts::CONTRACT_INITIAL_VERSION, ContractHash, ContractPackageHash, ContractVersionKey, ProtocolVersion, RuntimeArgs, diff --git a/grpc/tests/src/test/system_contracts/pos_install.rs b/grpc/tests/src/test/system_contracts/pos_install.rs index 46a13624d6..fefda626e5 100644 --- a/grpc/tests/src/test/system_contracts/pos_install.rs +++ b/grpc/tests/src/test/system_contracts/pos_install.rs @@ -10,7 +10,7 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::EngineConfig; +use node::components::contract_runtime::core::engine_state::EngineConfig; use types::{ account::AccountHash, contracts::NamedKeys, runtime_args, ContractHash, ContractPackageHash, Key, RuntimeArgs, URef, U512, diff --git a/grpc/tests/src/test/system_contracts/proof_of_stake/bonding.rs b/grpc/tests/src/test/system_contracts/proof_of_stake/bonding.rs index 96ab6bb22d..e3184dee2a 100644 --- a/grpc/tests/src/test/system_contracts/proof_of_stake/bonding.rs +++ b/grpc/tests/src/test/system_contracts/proof_of_stake/bonding.rs @@ -4,11 +4,11 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, }; -use node::contract_core::engine_state::{ +use node::components::contract_runtime::core::engine_state::{ genesis::{GenesisAccount, POS_BONDING_PURSE}, CONV_RATE, }; -use node::contract_shared::motes::Motes; +use node::components::contract_runtime::shared::motes::Motes; use types::{account::AccountHash, runtime_args, ApiError, Key, RuntimeArgs, URef, U512}; const CONTRACT_POS_BONDING: &str = "pos_bonding.wasm"; diff --git a/grpc/tests/src/test/system_contracts/proof_of_stake/commit_validators.rs b/grpc/tests/src/test/system_contracts/proof_of_stake/commit_validators.rs index 18c10d0e33..2d1945f7c4 100644 --- a/grpc/tests/src/test/system_contracts/proof_of_stake/commit_validators.rs +++ b/grpc/tests/src/test/system_contracts/proof_of_stake/commit_validators.rs @@ -5,8 +5,8 @@ use engine_test_support::{ internal::{utils, ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNTS}, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::genesis::GenesisAccount; -use node::contract_shared::motes::Motes; +use node::components::contract_runtime::core::engine_state::genesis::GenesisAccount; +use node::components::contract_runtime::shared::motes::Motes; use types::{account::AccountHash, RuntimeArgs, U512}; const CONTRACT_LOCAL_STATE: &str = "do_nothing.wasm"; diff --git a/grpc/tests/src/test/system_contracts/proof_of_stake/finalize_payment.rs b/grpc/tests/src/test/system_contracts/proof_of_stake/finalize_payment.rs index 94e75fea7e..c9ea5447fb 100644 --- a/grpc/tests/src/test/system_contracts/proof_of_stake/finalize_payment.rs +++ b/grpc/tests/src/test/system_contracts/proof_of_stake/finalize_payment.rs @@ -7,11 +7,11 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::{ +use node::components::contract_runtime::core::engine_state::{ genesis::{POS_PAYMENT_PURSE, POS_REWARDS_PURSE}, CONV_RATE, }; -use node::contract_shared::{account::Account, motes::Motes}; +use node::components::contract_runtime::shared::{account::Account, motes::Motes}; use types::{account::AccountHash, runtime_args, Key, RuntimeArgs, URef, U512}; const CONTRACT_FINALIZE_PAYMENT: &str = "pos_finalize_payment.wasm"; diff --git a/grpc/tests/src/test/system_contracts/standard_payment.rs b/grpc/tests/src/test/system_contracts/standard_payment.rs index 3bb93c8d12..7c2415581b 100644 --- a/grpc/tests/src/test/system_contracts/standard_payment.rs +++ b/grpc/tests/src/test/system_contracts/standard_payment.rs @@ -7,11 +7,11 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_INITIAL_BALANCE, }; -use node::contract_core::{ +use node::components::contract_runtime::core::{ engine_state::{genesis::POS_REWARDS_PURSE, Error, CONV_RATE, MAX_PAYMENT}, execution, }; -use node::contract_shared::{motes::Motes, transform::Transform}; +use node::components::contract_runtime::shared::{motes::Motes, transform::Transform}; use types::{account::AccountHash, runtime_args, ApiError, Key, RuntimeArgs, URef, U512}; const ACCOUNT_1_ADDR: AccountHash = AccountHash::new([42u8; 32]); diff --git a/grpc/tests/src/test/system_contracts/standard_payment_install.rs b/grpc/tests/src/test/system_contracts/standard_payment_install.rs index eaa1009980..50052d6774 100644 --- a/grpc/tests/src/test/system_contracts/standard_payment_install.rs +++ b/grpc/tests/src/test/system_contracts/standard_payment_install.rs @@ -4,8 +4,8 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::engine_state::EngineConfig; -use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use node::components::contract_runtime::core::engine_state::EngineConfig; +use node::components::contract_runtime::shared::{stored_value::StoredValue, transform::Transform}; use types::{runtime_args, ContractHash, RuntimeArgs}; const DEPLOY_HASH_1: [u8; 32] = [1u8; 32]; diff --git a/grpc/tests/src/test/system_contracts/upgrade.rs b/grpc/tests/src/test/system_contracts/upgrade.rs index 800d8fd240..45bf543c82 100644 --- a/grpc/tests/src/test/system_contracts/upgrade.rs +++ b/grpc/tests/src/test/system_contracts/upgrade.rs @@ -5,10 +5,10 @@ use engine_test_support::internal::{ }; #[cfg(feature = "use-system-contracts")] use engine_test_support::{internal::ExecuteRequestBuilder, DEFAULT_ACCOUNT_ADDR}; -use node::contract_core::engine_state::{upgrade::ActivationPoint, Error}; -use node::contract_shared::wasm_costs::WasmCosts; +use node::components::contract_runtime::core::engine_state::{upgrade::ActivationPoint, Error}; +use node::components::contract_runtime::shared::wasm_costs::WasmCosts; #[cfg(feature = "use-system-contracts")] -use node::contract_shared::{stored_value::StoredValue, transform::Transform}; +use node::components::contract_runtime::shared::{stored_value::StoredValue, transform::Transform}; use types::ProtocolVersion; #[cfg(feature = "use-system-contracts")] use types::{runtime_args, CLValue, Key, RuntimeArgs, U512}; diff --git a/grpc/tests/src/test/upgrade.rs b/grpc/tests/src/test/upgrade.rs index fa2a34af25..13a2532f11 100644 --- a/grpc/tests/src/test/upgrade.rs +++ b/grpc/tests/src/test/upgrade.rs @@ -2,7 +2,7 @@ use engine_test_support::{ internal::{ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_RUN_GENESIS_REQUEST}, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_shared::stored_value::StoredValue; +use node::components::contract_runtime::shared::stored_value::StoredValue; use types::{ contracts::{ContractVersion, CONTRACT_INITIAL_VERSION}, runtime_args, CLValue, ContractPackageHash, RuntimeArgs, diff --git a/grpc/tests/src/test/wasmless_transfer.rs b/grpc/tests/src/test/wasmless_transfer.rs index 771892330b..90c039f243 100644 --- a/grpc/tests/src/test/wasmless_transfer.rs +++ b/grpc/tests/src/test/wasmless_transfer.rs @@ -7,7 +7,9 @@ use engine_test_support::{ }, DEFAULT_ACCOUNT_ADDR, }; -use node::contract_core::{engine_state::Error as CoreError, execution::Error as ExecError}; +use node::components::contract_runtime::core::{ + engine_state::Error as CoreError, execution::Error as ExecError, +}; use types::{ account::AccountHash, runtime_args, AccessRights, ApiError, Key, RuntimeArgs, URef, U512, }; diff --git a/node/Cargo.toml b/node/Cargo.toml index 44ea15ea77..5c58e77482 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -42,7 +42,6 @@ parity-wasm = "0.41.0" pwasm-utils = "0.12.0" rand = "0.7.3" rand_chacha = "0.2.2" -reqwest = "0.10.6" rmp-serde = "0.14.3" serde = { version = "1.0.110", features = ["derive"] } serde_json = "1.0.55" @@ -90,14 +89,7 @@ gens = ["proptest"] [[bin]] name = "casperlabs-node" -path = "src/apps/node/main.rs" -bench = false -doctest = false -test = false - -[[bin]] -name = "casperlabs-client" -path = "src/apps/client/main.rs" +path = "src/app/main.rs" bench = false doctest = false test = false diff --git a/node/benches/trie_bench.rs b/node/benches/trie_bench.rs index ce26cddfeb..3f70d41109 100644 --- a/node/benches/trie_bench.rs +++ b/node/benches/trie_bench.rs @@ -1,7 +1,9 @@ use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion}; -use casperlabs_node::contract_shared::{newtypes::Blake2bHash, stored_value::StoredValue}; -use casperlabs_node::contract_storage::trie::{Pointer, PointerBlock, Trie}; +use casperlabs_node::components::contract_runtime::shared::{ + newtypes::Blake2bHash, stored_value::StoredValue, +}; +use casperlabs_node::components::contract_runtime::storage::trie::{Pointer, PointerBlock, Trie}; use types::{ account::AccountHash, bytesrepr::{FromBytes, ToBytes}, diff --git a/node/src/apps/node/cli.rs b/node/src/app/cli.rs similarity index 100% rename from node/src/apps/node/cli.rs rename to node/src/app/cli.rs diff --git a/node/src/apps/node/config.rs b/node/src/app/config.rs similarity index 100% rename from node/src/apps/node/config.rs rename to node/src/app/config.rs diff --git a/node/src/apps/node/logging.rs b/node/src/app/logging.rs similarity index 100% rename from node/src/apps/node/logging.rs rename to node/src/app/logging.rs diff --git a/node/src/apps/node/main.rs b/node/src/app/main.rs similarity index 100% rename from node/src/apps/node/main.rs rename to node/src/app/main.rs diff --git a/node/src/components/contract_runtime.rs b/node/src/components/contract_runtime.rs index 5eb3e54c78..4de9e39136 100644 --- a/node/src/components/contract_runtime.rs +++ b/node/src/components/contract_runtime.rs @@ -1,5 +1,8 @@ //! Contract Runtime component. mod config; +pub mod core; +pub mod shared; +pub mod storage; use std::{ fmt::{Debug, Display}, @@ -11,9 +14,9 @@ use lmdb::DatabaseFlags; use rand::Rng; use serde::{Deserialize, Serialize}; -use crate::contract_core::engine_state::{EngineConfig, EngineState}; -use crate::contract_storage::protocol_data_store::lmdb::LmdbProtocolDataStore; -use crate::contract_storage::{ +use crate::components::contract_runtime::core::engine_state::{EngineConfig, EngineState}; +use crate::components::contract_runtime::storage::protocol_data_store::lmdb::LmdbProtocolDataStore; +use crate::components::contract_runtime::storage::{ global_state::lmdb::LmdbGlobalState, transaction_source::lmdb::LmdbEnvironment, trie_store::lmdb::LmdbTrieStore, }; diff --git a/node/src/components/contract_runtime/config.rs b/node/src/components/contract_runtime/config.rs index 1277dabefc..9ce5ae8002 100644 --- a/node/src/components/contract_runtime/config.rs +++ b/node/src/components/contract_runtime/config.rs @@ -1,4 +1,4 @@ -use crate::contract_shared::page_size; +use crate::components::contract_runtime::shared::page_size; use serde::{Deserialize, Serialize}; // 750 GiB = 805306368000 bytes diff --git a/node/src/contract_core.rs b/node/src/components/contract_runtime/core.rs similarity index 100% rename from node/src/contract_core.rs rename to node/src/components/contract_runtime/core.rs diff --git a/node/src/contract_core/engine_state/deploy_item.rs b/node/src/components/contract_runtime/core/engine_state/deploy_item.rs similarity index 95% rename from node/src/contract_core/engine_state/deploy_item.rs rename to node/src/components/contract_runtime/core/engine_state/deploy_item.rs index 32a0ccc5cb..bd5716b16f 100644 --- a/node/src/contract_core/engine_state/deploy_item.rs +++ b/node/src/components/contract_runtime/core/engine_state/deploy_item.rs @@ -2,7 +2,7 @@ use std::collections::BTreeSet; use types::account::AccountHash; -use crate::contract_core::{ +use crate::components::contract_runtime::core::{ engine_state::executable_deploy_item::ExecutableDeployItem, DeployHash, }; diff --git a/node/src/contract_core/engine_state/engine_config.rs b/node/src/components/contract_runtime/core/engine_state/engine_config.rs similarity index 100% rename from node/src/contract_core/engine_state/engine_config.rs rename to node/src/components/contract_runtime/core/engine_state/engine_config.rs diff --git a/node/src/contract_core/engine_state/error.rs b/node/src/components/contract_runtime/core/engine_state/error.rs similarity index 88% rename from node/src/contract_core/engine_state/error.rs rename to node/src/components/contract_runtime/core/engine_state/error.rs index f251987541..1613650585 100644 --- a/node/src/contract_core/engine_state/error.rs +++ b/node/src/components/contract_runtime/core/engine_state/error.rs @@ -1,12 +1,12 @@ use thiserror::Error; -use crate::contract_shared::newtypes::Blake2bHash; -use crate::contract_shared::wasm_prep; +use crate::components::contract_runtime::shared::newtypes::Blake2bHash; +use crate::components::contract_runtime::shared::wasm_prep; use types::ProtocolVersion; use types::{bytesrepr, system_contract_errors::mint}; -use crate::contract_core::execution; -use crate::contract_storage; +use crate::components::contract_runtime::core::execution; +use crate::components::contract_runtime::storage; #[derive(Error, Debug)] pub enum Error { @@ -25,7 +25,7 @@ pub enum Error { #[error(transparent)] Exec(execution::Error), #[error("Storage error: {}", _0)] - Storage(contract_storage::error::Error), + Storage(storage::error::Error), #[error("Authorization failure: not authorized.")] Authorization, #[error("Insufficient payment")] @@ -71,8 +71,8 @@ impl From for Error { } } -impl From for Error { - fn from(error: contract_storage::error::Error) -> Self { +impl From for Error { + fn from(error: storage::error::Error) -> Self { Error::Storage(error) } } diff --git a/node/src/contract_core/engine_state/executable_deploy_item.rs b/node/src/components/contract_runtime/core/engine_state/executable_deploy_item.rs similarity index 96% rename from node/src/contract_core/engine_state/executable_deploy_item.rs rename to node/src/components/contract_runtime/core/engine_state/executable_deploy_item.rs index ea8b3e0770..623e9ba56b 100644 --- a/node/src/contract_core/engine_state/executable_deploy_item.rs +++ b/node/src/components/contract_runtime/core/engine_state/executable_deploy_item.rs @@ -1,6 +1,6 @@ use super::error; -use crate::contract_core::execution; -use crate::contract_shared::account::Account; +use crate::components::contract_runtime::core::execution; +use crate::components::contract_runtime::shared::account::Account; use types::{ bytesrepr, contracts::{ContractVersion, DEFAULT_ENTRY_POINT_NAME}, diff --git a/node/src/contract_core/engine_state/execute_request.rs b/node/src/components/contract_runtime/core/engine_state/execute_request.rs similarity index 93% rename from node/src/contract_core/engine_state/execute_request.rs rename to node/src/components/contract_runtime/core/engine_state/execute_request.rs index 3e59829008..79b062de3e 100644 --- a/node/src/contract_core/engine_state/execute_request.rs +++ b/node/src/components/contract_runtime/core/engine_state/execute_request.rs @@ -1,6 +1,6 @@ use std::mem; -use crate::contract_shared::newtypes::Blake2bHash; +use crate::components::contract_runtime::shared::newtypes::Blake2bHash; use types::ProtocolVersion; use super::{deploy_item::DeployItem, execution_result::ExecutionResult}; diff --git a/node/src/contract_core/engine_state/execution_effect.rs b/node/src/components/contract_runtime/core/engine_state/execution_effect.rs similarity index 77% rename from node/src/contract_core/engine_state/execution_effect.rs rename to node/src/components/contract_runtime/core/engine_state/execution_effect.rs index d88ba4834c..a4a1c69c49 100644 --- a/node/src/contract_core/engine_state/execution_effect.rs +++ b/node/src/components/contract_runtime/core/engine_state/execution_effect.rs @@ -1,16 +1,18 @@ -use crate::contract_shared::{additive_map::AdditiveMap, transform::Transform}; -use types::Key; - -use super::op::Op; - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct ExecutionEffect { - pub ops: AdditiveMap, - pub transforms: AdditiveMap, -} - -impl ExecutionEffect { - pub fn new(ops: AdditiveMap, transforms: AdditiveMap) -> Self { - ExecutionEffect { ops, transforms } - } -} +use crate::components::contract_runtime::shared::{ + additive_map::AdditiveMap, transform::Transform, +}; +use types::Key; + +use super::op::Op; + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ExecutionEffect { + pub ops: AdditiveMap, + pub transforms: AdditiveMap, +} + +impl ExecutionEffect { + pub fn new(ops: AdditiveMap, transforms: AdditiveMap) -> Self { + ExecutionEffect { ops, transforms } + } +} diff --git a/node/src/contract_core/engine_state/execution_result.rs b/node/src/components/contract_runtime/core/engine_state/execution_result.rs similarity index 98% rename from node/src/contract_core/engine_state/execution_result.rs rename to node/src/components/contract_runtime/core/engine_state/execution_result.rs index 3eed1d2ec4..e3657a98a9 100644 --- a/node/src/contract_core/engine_state/execution_result.rs +++ b/node/src/components/contract_runtime/core/engine_state/execution_result.rs @@ -1,9 +1,9 @@ use super::{error, execution_effect::ExecutionEffect, op::Op, CONV_RATE}; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ additive_map::AdditiveMap, gas::Gas, motes::Motes, newtypes::CorrelationId, stored_value::StoredValue, transform::Transform, }; -use crate::contract_storage::global_state::StateReader; +use crate::components::contract_runtime::storage::global_state::StateReader; use types::{bytesrepr::FromBytes, CLTyped, CLValue, Key}; fn make_payment_error_effects( diff --git a/node/src/contract_core/engine_state/genesis.rs b/node/src/components/contract_runtime/core/engine_state/genesis.rs similarity index 95% rename from node/src/contract_core/engine_state/genesis.rs rename to node/src/components/contract_runtime/core/engine_state/genesis.rs index 180ef13819..92353983d7 100644 --- a/node/src/contract_core/engine_state/genesis.rs +++ b/node/src/components/contract_runtime/core/engine_state/genesis.rs @@ -6,12 +6,14 @@ use rand::{ Rng, }; -use crate::contract_shared::wasm_costs::WasmCosts; -use crate::contract_shared::{motes::Motes, newtypes::Blake2bHash, TypeMismatch}; -use crate::contract_storage::global_state::CommitResult; +use crate::components::contract_runtime::shared::wasm_costs::WasmCosts; +use crate::components::contract_runtime::shared::{ + motes::Motes, newtypes::Blake2bHash, TypeMismatch, +}; +use crate::components::contract_runtime::storage::global_state::CommitResult; use types::{account::AccountHash, bytesrepr, Key, ProtocolVersion, U512}; -use crate::contract_core::engine_state::execution_effect::ExecutionEffect; +use crate::components::contract_runtime::core::engine_state::execution_effect::ExecutionEffect; pub const PLACEHOLDER_KEY: Key = Key::Hash([0u8; 32]); pub const POS_BONDING_PURSE: &str = "pos_bonding_purse"; diff --git a/node/src/contract_core/engine_state/mod.rs b/node/src/components/contract_runtime/core/engine_state/mod.rs similarity index 99% rename from node/src/contract_core/engine_state/mod.rs rename to node/src/components/contract_runtime/core/engine_state/mod.rs index 522cee5280..18e1a898fd 100644 --- a/node/src/contract_core/engine_state/mod.rs +++ b/node/src/components/contract_runtime/core/engine_state/mod.rs @@ -24,9 +24,9 @@ use num_traits::Zero; use parity_wasm::elements::Module; use tracing::{debug, warn}; -use crate::contract_shared::wasm_costs::WasmCosts; -use crate::contract_shared::wasm_prep::{self, Preprocessor}; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::wasm_costs::WasmCosts; +use crate::components::contract_runtime::shared::wasm_prep::{self, Preprocessor}; +use crate::components::contract_runtime::shared::{ account::Account, additive_map::AdditiveMap, gas::Gas, @@ -35,7 +35,7 @@ use crate::contract_shared::{ stored_value::StoredValue, transform::Transform, }; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ global_state::{CommitResult, StateProvider, StateReader}, protocol_data::ProtocolData, }; @@ -56,7 +56,7 @@ pub use self::{ error::{Error, RootNotFound}, transfer::TransferRuntimeArgsBuilder, }; -use crate::contract_core::{ +use crate::components::contract_runtime::core::{ engine_state::{ deploy_item::DeployItem, error::Error::MissingSystemContract, diff --git a/node/src/contract_core/engine_state/op.rs b/node/src/components/contract_runtime/core/engine_state/op.rs similarity index 100% rename from node/src/contract_core/engine_state/op.rs rename to node/src/components/contract_runtime/core/engine_state/op.rs diff --git a/node/src/contract_core/engine_state/query.rs b/node/src/components/contract_runtime/core/engine_state/query.rs similarity index 85% rename from node/src/contract_core/engine_state/query.rs rename to node/src/components/contract_runtime/core/engine_state/query.rs index 81c9b61c54..810158b2be 100644 --- a/node/src/contract_core/engine_state/query.rs +++ b/node/src/components/contract_runtime/core/engine_state/query.rs @@ -1,7 +1,9 @@ -use crate::contract_shared::{newtypes::Blake2bHash, stored_value::StoredValue}; +use crate::components::contract_runtime::shared::{ + newtypes::Blake2bHash, stored_value::StoredValue, +}; use types::Key; -use crate::contract_core::tracking_copy::TrackingCopyQueryResult; +use crate::components::contract_runtime::core::tracking_copy::TrackingCopyQueryResult; pub enum QueryResult { RootNotFound, diff --git a/node/src/contract_core/engine_state/run_genesis_request.rs b/node/src/components/contract_runtime/core/engine_state/run_genesis_request.rs similarity index 94% rename from node/src/contract_core/engine_state/run_genesis_request.rs rename to node/src/components/contract_runtime/core/engine_state/run_genesis_request.rs index 26a5e8e6f4..5e84cf022e 100644 --- a/node/src/contract_core/engine_state/run_genesis_request.rs +++ b/node/src/components/contract_runtime/core/engine_state/run_genesis_request.rs @@ -5,7 +5,7 @@ use rand::{ use std::{convert::TryInto, iter}; use super::genesis::ExecConfig; -use crate::contract_shared::newtypes::{Blake2bHash, BLAKE2B_DIGEST_LENGTH}; +use crate::components::contract_runtime::shared::newtypes::{Blake2bHash, BLAKE2B_DIGEST_LENGTH}; use types::ProtocolVersion; #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/node/src/contract_core/engine_state/system_contract_cache.rs b/node/src/components/contract_runtime/core/engine_state/system_contract_cache.rs similarity index 99% rename from node/src/contract_core/engine_state/system_contract_cache.rs rename to node/src/components/contract_runtime/core/engine_state/system_contract_cache.rs index 52fdd191c5..1385ce8d7e 100644 --- a/node/src/contract_core/engine_state/system_contract_cache.rs +++ b/node/src/components/contract_runtime/core/engine_state/system_contract_cache.rs @@ -42,7 +42,7 @@ mod tests { use lazy_static::lazy_static; use parity_wasm::elements::{Module, ModuleNameSubsection, NameSection, Section}; - use crate::contract_core::{ + use crate::components::contract_runtime::core::{ engine_state::system_contract_cache::SystemContractCache, execution::{AddressGenerator, AddressGeneratorBuilder}, }; diff --git a/node/src/contract_core/engine_state/transfer.rs b/node/src/components/contract_runtime/core/engine_state/transfer.rs similarity index 96% rename from node/src/contract_core/engine_state/transfer.rs rename to node/src/components/contract_runtime/core/engine_state/transfer.rs index 8ebad021a0..ce2f41ab38 100644 --- a/node/src/contract_core/engine_state/transfer.rs +++ b/node/src/components/contract_runtime/core/engine_state/transfer.rs @@ -1,16 +1,16 @@ -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ account::Account, newtypes::CorrelationId, stored_value::StoredValue, }; -use crate::contract_storage::global_state::StateReader; +use crate::components::contract_runtime::storage::global_state::StateReader; use std::{cell::RefCell, rc::Rc}; use types::{account::AccountHash, AccessRights, ApiError, Key, RuntimeArgs, URef, U512}; -use crate::contract_core::{ +use crate::components::contract_runtime::core::{ engine_state::Error, execution::Error as ExecError, tracking_copy::{TrackingCopy, TrackingCopyExt}, }; -use crate::contract_shared; +use crate::components::contract_runtime::shared; const SOURCE: &str = "source"; const TARGET: &str = "target"; @@ -102,10 +102,7 @@ impl TransferRuntimeArgsBuilder { } } Some(key) => Err(Error::Exec(ExecError::TypeMismatch( - contract_shared::TypeMismatch::new( - "Key::URef".to_string(), - key.type_string(), - ), + shared::TypeMismatch::new("Key::URef".to_string(), key.type_string()), ))), None => Err(Error::Exec(ExecError::ForgedReference(uref))), } diff --git a/node/src/contract_core/engine_state/upgrade.rs b/node/src/components/contract_runtime/core/engine_state/upgrade.rs similarity index 91% rename from node/src/contract_core/engine_state/upgrade.rs rename to node/src/components/contract_runtime/core/engine_state/upgrade.rs index a760300362..fd7a393128 100644 --- a/node/src/contract_core/engine_state/upgrade.rs +++ b/node/src/components/contract_runtime/core/engine_state/upgrade.rs @@ -1,11 +1,11 @@ use std::fmt; -use crate::contract_shared::wasm_costs::WasmCosts; -use crate::contract_shared::{newtypes::Blake2bHash, TypeMismatch}; -use crate::contract_storage::global_state::CommitResult; +use crate::components::contract_runtime::shared::wasm_costs::WasmCosts; +use crate::components::contract_runtime::shared::{newtypes::Blake2bHash, TypeMismatch}; +use crate::components::contract_runtime::storage::global_state::CommitResult; use types::{bytesrepr, Key, ProtocolVersion}; -use crate::contract_core::engine_state::execution_effect::ExecutionEffect; +use crate::components::contract_runtime::core::engine_state::execution_effect::ExecutionEffect; pub type ActivationPoint = u64; diff --git a/node/src/contract_core/engine_state/utils.rs b/node/src/components/contract_runtime/core/engine_state/utils.rs similarity index 100% rename from node/src/contract_core/engine_state/utils.rs rename to node/src/components/contract_runtime/core/engine_state/utils.rs diff --git a/node/src/contract_core/execution/address_generator.rs b/node/src/components/contract_runtime/core/execution/address_generator.rs similarity index 97% rename from node/src/contract_core/execution/address_generator.rs rename to node/src/components/contract_runtime/core/execution/address_generator.rs index 2df79e8ee5..2ff7131c16 100644 --- a/node/src/contract_core/execution/address_generator.rs +++ b/node/src/components/contract_runtime/core/execution/address_generator.rs @@ -7,7 +7,7 @@ use rand_chacha::ChaChaRng; use types::Phase; -use crate::contract_core::{Address, ADDRESS_LENGTH}; +use crate::components::contract_runtime::core::{Address, ADDRESS_LENGTH}; const SEED_LENGTH: usize = 32; diff --git a/node/src/contract_core/execution/error.rs b/node/src/components/contract_runtime/core/execution/error.rs similarity index 93% rename from node/src/contract_core/execution/error.rs rename to node/src/components/contract_runtime/core/execution/error.rs index d3fa78b20d..3a72ba36e5 100644 --- a/node/src/contract_core/execution/error.rs +++ b/node/src/components/contract_runtime/core/execution/error.rs @@ -1,22 +1,22 @@ use parity_wasm::elements; use thiserror::Error; -use crate::contract_shared::{wasm_prep, TypeMismatch}; +use crate::components::contract_runtime::shared::{wasm_prep, TypeMismatch}; use types::{ account::{AddKeyFailure, RemoveKeyFailure, SetThresholdFailure, UpdateKeyFailure}, bytesrepr, system_contract_errors, AccessRights, ApiError, CLType, CLValueError, ContractPackageHash, ContractVersionKey, Key, URef, }; -use crate::contract_core::resolvers::error::ResolverError; -use crate::contract_storage; +use crate::components::contract_runtime::core::resolvers::error::ResolverError; +use crate::components::contract_runtime::storage; #[derive(Error, Debug, Clone)] pub enum Error { #[error("Interpreter error: {}", _0)] Interpreter(String), #[error("Storage error: {}", _0)] - Storage(contract_storage::error::Error), + Storage(storage::error::Error), #[error("Serialization error: {}", _0)] BytesRepr(bytesrepr::Error), #[error("Named key {} not found", _0)] @@ -115,8 +115,8 @@ impl From for Error { } } -impl From for Error { - fn from(e: contract_storage::error::Error) -> Self { +impl From for Error { + fn from(e: storage::error::Error) -> Self { Error::Storage(e) } } diff --git a/node/src/contract_core/execution/executor.rs b/node/src/components/contract_runtime/core/execution/executor.rs similarity index 99% rename from node/src/contract_core/execution/executor.rs rename to node/src/components/contract_runtime/core/execution/executor.rs index 188b9885aa..fc74a53a2f 100644 --- a/node/src/contract_core/execution/executor.rs +++ b/node/src/components/contract_runtime/core/execution/executor.rs @@ -4,17 +4,19 @@ use parity_wasm::elements::Module; use tracing::warn; use wasmi::ModuleRef; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ account::Account, gas::Gas, newtypes::CorrelationId, stored_value::StoredValue, }; -use crate::contract_storage::{global_state::StateReader, protocol_data::ProtocolData}; +use crate::components::contract_runtime::storage::{ + global_state::StateReader, protocol_data::ProtocolData, +}; use types::{ account::AccountHash, bytesrepr::FromBytes, contracts::NamedKeys, AccessRights, BlockTime, CLTyped, CLValue, ContractPackage, EntryPoint, EntryPointType, Key, Phase, ProtocolVersion, RuntimeArgs, }; -use crate::contract_core::{ +use crate::components::contract_runtime::core::{ engine_state::{ execution_effect::ExecutionEffect, execution_result::ExecutionResult, system_contract_cache::SystemContractCache, EngineConfig, diff --git a/node/src/contract_core/execution/mod.rs b/node/src/components/contract_runtime/core/execution/mod.rs similarity index 100% rename from node/src/contract_core/execution/mod.rs rename to node/src/components/contract_runtime/core/execution/mod.rs diff --git a/node/src/contract_core/execution/tests.rs b/node/src/components/contract_runtime/core/execution/tests.rs similarity index 93% rename from node/src/contract_core/execution/tests.rs rename to node/src/components/contract_runtime/core/execution/tests.rs index d875fc31e0..0e87d881f4 100644 --- a/node/src/contract_core/execution/tests.rs +++ b/node/src/components/contract_runtime/core/execution/tests.rs @@ -1,9 +1,9 @@ -use crate::contract_shared::{gas::Gas, transform::Transform}; +use crate::components::contract_runtime::shared::{gas::Gas, transform::Transform}; use tracing::warn; use types::{Key, U512}; use super::Error; -use crate::contract_core::engine_state::{ +use crate::components::contract_runtime::core::engine_state::{ execution_effect::ExecutionEffect, execution_result::ExecutionResult, op::Op, }; diff --git a/node/src/contract_core/resolvers/error.rs b/node/src/components/contract_runtime/core/resolvers/error.rs similarity index 100% rename from node/src/contract_core/resolvers/error.rs rename to node/src/components/contract_runtime/core/resolvers/error.rs diff --git a/node/src/contract_core/resolvers/memory_resolver.rs b/node/src/components/contract_runtime/core/resolvers/memory_resolver.rs similarity index 100% rename from node/src/contract_core/resolvers/memory_resolver.rs rename to node/src/components/contract_runtime/core/resolvers/memory_resolver.rs diff --git a/node/src/contract_core/resolvers/mod.rs b/node/src/components/contract_runtime/core/resolvers/mod.rs similarity index 91% rename from node/src/contract_core/resolvers/mod.rs rename to node/src/components/contract_runtime/core/resolvers/mod.rs index a75ece1e1d..3915ffeadb 100644 --- a/node/src/contract_core/resolvers/mod.rs +++ b/node/src/components/contract_runtime/core/resolvers/mod.rs @@ -8,7 +8,7 @@ use wasmi::ModuleImportResolver; use types::ProtocolVersion; use self::error::ResolverError; -use crate::contract_core::resolvers::memory_resolver::MemoryResolver; +use crate::components::contract_runtime::core::resolvers::memory_resolver::MemoryResolver; /// Creates a module resolver for given protocol version. /// diff --git a/node/src/contract_core/resolvers/v1_function_index.rs b/node/src/components/contract_runtime/core/resolvers/v1_function_index.rs similarity index 100% rename from node/src/contract_core/resolvers/v1_function_index.rs rename to node/src/components/contract_runtime/core/resolvers/v1_function_index.rs diff --git a/node/src/contract_core/resolvers/v1_resolver.rs b/node/src/components/contract_runtime/core/resolvers/v1_resolver.rs similarity index 100% rename from node/src/contract_core/resolvers/v1_resolver.rs rename to node/src/components/contract_runtime/core/resolvers/v1_resolver.rs diff --git a/node/src/contract_core/runtime/args.rs b/node/src/components/contract_runtime/core/runtime/args.rs similarity index 100% rename from node/src/contract_core/runtime/args.rs rename to node/src/components/contract_runtime/core/runtime/args.rs diff --git a/node/src/contract_core/runtime/externals.rs b/node/src/components/contract_runtime/core/runtime/externals.rs similarity index 99% rename from node/src/contract_core/runtime/externals.rs rename to node/src/components/contract_runtime/core/runtime/externals.rs index 0fce2985f8..818932c999 100644 --- a/node/src/contract_core/runtime/externals.rs +++ b/node/src/components/contract_runtime/core/runtime/externals.rs @@ -10,11 +10,11 @@ use types::{ ContractHash, ContractPackageHash, ContractVersion, Group, Key, TransferredTo, URef, U512, }; -use crate::contract_shared::{gas::Gas, stored_value::StoredValue}; -use crate::contract_storage::global_state::StateReader; +use crate::components::contract_runtime::shared::{gas::Gas, stored_value::StoredValue}; +use crate::components::contract_runtime::storage::global_state::StateReader; use super::{args::Args, scoped_instrumenter::ScopedInstrumenter, Error, Runtime}; -use crate::contract_core::resolvers::v1_function_index::FunctionIndex; +use crate::components::contract_runtime::core::resolvers::v1_function_index::FunctionIndex; impl<'a, R> Externals for Runtime<'a, R> where diff --git a/node/src/contract_core/runtime/mint_internal.rs b/node/src/components/contract_runtime/core/runtime/mint_internal.rs similarity index 92% rename from node/src/contract_core/runtime/mint_internal.rs rename to node/src/components/contract_runtime/core/runtime/mint_internal.rs index 6b2cf6082f..14a7ead751 100644 --- a/node/src/contract_core/runtime/mint_internal.rs +++ b/node/src/components/contract_runtime/core/runtime/mint_internal.rs @@ -1,5 +1,5 @@ -use crate::contract_shared::stored_value::StoredValue; -use crate::contract_storage::global_state::StateReader; +use crate::components::contract_runtime::shared::stored_value::StoredValue; +use crate::components::contract_runtime::storage::global_state::StateReader; use types::mint::{Mint, RuntimeProvider, StorageProvider}; use types::{ account::AccountHash, @@ -8,7 +8,7 @@ use types::{ CLTyped, CLValue, Key, URef, }; -use crate::contract_core::{execution, runtime_context::RuntimeContext}; +use crate::components::contract_runtime::core::{execution, runtime_context::RuntimeContext}; impl<'a, R> RuntimeProvider for RuntimeContext<'a, R> where diff --git a/node/src/contract_core/runtime/mod.rs b/node/src/components/contract_runtime/core/runtime/mod.rs similarity index 99% rename from node/src/contract_core/runtime/mod.rs rename to node/src/components/contract_runtime/core/runtime/mod.rs index 336bc833cd..a26ffb4a12 100644 --- a/node/src/contract_core/runtime/mod.rs +++ b/node/src/components/contract_runtime/core/runtime/mod.rs @@ -16,8 +16,12 @@ use itertools::Itertools; use parity_wasm::elements::Module; use wasmi::{ImportsBuilder, MemoryRef, ModuleInstance, ModuleRef, Trap, TrapKind}; -use crate::contract_shared::{account::Account, gas::Gas, stored_value::StoredValue}; -use crate::contract_storage::{global_state::StateReader, protocol_data::ProtocolData}; +use crate::components::contract_runtime::shared::{ + account::Account, gas::Gas, stored_value::StoredValue, +}; +use crate::components::contract_runtime::storage::{ + global_state::StateReader, protocol_data::ProtocolData, +}; use types::mint::Mint; use types::proof_of_stake::ProofOfStake; use types::standard_payment::StandardPayment; @@ -34,7 +38,7 @@ use types::{ SystemContractType, TransferResult, TransferredTo, URef, U128, U256, U512, }; -use crate::contract_core::{ +use crate::components::contract_runtime::core::{ engine_state::{system_contract_cache::SystemContractCache, EngineConfig}, execution::Error, resolvers::{create_module_resolver, memory_resolver::MemoryResolver}, diff --git a/node/src/contract_core/runtime/proof_of_stake_internal.rs b/node/src/components/contract_runtime/core/runtime/proof_of_stake_internal.rs similarity index 96% rename from node/src/contract_core/runtime/proof_of_stake_internal.rs rename to node/src/components/contract_runtime/core/runtime/proof_of_stake_internal.rs index 6fb2994005..18cdd8365c 100644 --- a/node/src/contract_core/runtime/proof_of_stake_internal.rs +++ b/node/src/components/contract_runtime/core/runtime/proof_of_stake_internal.rs @@ -3,8 +3,8 @@ use std::{ fmt::Write, }; -use crate::contract_shared::stored_value::StoredValue; -use crate::contract_storage::global_state::StateReader; +use crate::components::contract_runtime::shared::stored_value::StoredValue; +use crate::components::contract_runtime::storage::global_state::StateReader; use types::proof_of_stake::{ MintProvider, ProofOfStake, Queue, QueueProvider, RuntimeProvider, Stakes, StakesProvider, }; @@ -13,7 +13,7 @@ use types::{ BlockTime, CLValue, Key, Phase, TransferredTo, URef, U512, }; -use crate::contract_core::{execution, runtime::Runtime}; +use crate::components::contract_runtime::core::{execution, runtime::Runtime}; const BONDING_KEY: [u8; 32] = { let mut result = [0; 32]; diff --git a/node/src/contract_core/runtime/scoped_instrumenter.rs b/node/src/components/contract_runtime/core/runtime/scoped_instrumenter.rs similarity index 97% rename from node/src/contract_core/runtime/scoped_instrumenter.rs rename to node/src/components/contract_runtime/core/runtime/scoped_instrumenter.rs index be7e07fac6..467ab581c1 100644 --- a/node/src/contract_core/runtime/scoped_instrumenter.rs +++ b/node/src/components/contract_runtime/core/runtime/scoped_instrumenter.rs @@ -4,9 +4,9 @@ use std::{ time::{Duration, Instant}, }; -use crate::contract_shared::logging::log_host_function_metrics; +use crate::components::contract_runtime::shared::logging::log_host_function_metrics; -use crate::contract_core::resolvers::v1_function_index::FunctionIndex; +use crate::components::contract_runtime::core::resolvers::v1_function_index::FunctionIndex; enum PauseState { NotStarted, diff --git a/node/src/contract_core/runtime/standard_payment_internal.rs b/node/src/components/contract_runtime/core/runtime/standard_payment_internal.rs similarity index 90% rename from node/src/contract_core/runtime/standard_payment_internal.rs rename to node/src/components/contract_runtime/core/runtime/standard_payment_internal.rs index f9820126c5..c2474cbecd 100644 --- a/node/src/contract_core/runtime/standard_payment_internal.rs +++ b/node/src/components/contract_runtime/core/runtime/standard_payment_internal.rs @@ -1,11 +1,11 @@ -use crate::contract_shared::stored_value::StoredValue; -use crate::contract_storage::global_state::StateReader; +use crate::components::contract_runtime::shared::stored_value::StoredValue; +use crate::components::contract_runtime::storage::global_state::StateReader; use types::standard_payment::{ AccountProvider, MintProvider, ProofOfStakeProvider, StandardPayment, }; use types::{system_contract_errors, ApiError, Key, RuntimeArgs, URef, U512}; -use crate::contract_core::{execution, runtime::Runtime}; +use crate::components::contract_runtime::core::{execution, runtime::Runtime}; pub(crate) const METHOD_GET_PAYMENT_PURSE: &str = "get_payment_purse"; diff --git a/node/src/contract_core/runtime_context/mod.rs b/node/src/components/contract_runtime/core/runtime_context/mod.rs similarity index 99% rename from node/src/contract_core/runtime_context/mod.rs rename to node/src/components/contract_runtime/core/runtime_context/mod.rs index 5b904084b7..35ce33feee 100644 --- a/node/src/contract_core/runtime_context/mod.rs +++ b/node/src/components/contract_runtime/core/runtime_context/mod.rs @@ -11,10 +11,12 @@ use blake2::{ VarBlake2b, }; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ account::Account, gas::Gas, newtypes::CorrelationId, stored_value::StoredValue, }; -use crate::contract_storage::{global_state::StateReader, protocol_data::ProtocolData}; +use crate::components::contract_runtime::storage::{ + global_state::StateReader, protocol_data::ProtocolData, +}; use types::{ account::{ AccountHash, ActionType, AddKeyFailure, RemoveKeyFailure, SetThresholdFailure, @@ -27,7 +29,7 @@ use types::{ KEY_HASH_LENGTH, }; -use crate::contract_core::{ +use crate::components::contract_runtime::core::{ engine_state::execution_effect::ExecutionEffect, execution::{AddressGenerator, Error}, tracking_copy::{AddResult, TrackingCopy}, diff --git a/node/src/contract_core/runtime_context/tests.rs b/node/src/components/contract_runtime/core/runtime_context/tests.rs similarity index 99% rename from node/src/contract_core/runtime_context/tests.rs rename to node/src/components/contract_runtime/core/runtime_context/tests.rs index 6c0fff5bc8..cd90d3df7e 100644 --- a/node/src/contract_core/runtime_context/tests.rs +++ b/node/src/components/contract_runtime/core/runtime_context/tests.rs @@ -7,7 +7,7 @@ use std::{ use rand::RngCore; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ account::{Account, AssociatedKeys}, additive_map::AdditiveMap, gas::Gas, @@ -15,7 +15,7 @@ use crate::contract_shared::{ stored_value::StoredValue, transform::Transform, }; -use crate::contract_storage::global_state::{ +use crate::components::contract_runtime::storage::global_state::{ in_memory::{InMemoryGlobalState, InMemoryGlobalStateView}, CommitResult, StateProvider, }; @@ -29,7 +29,7 @@ use types::{ }; use super::{Address, Error, RuntimeContext}; -use crate::contract_core::{ +use crate::components::contract_runtime::core::{ execution::AddressGenerator, runtime::extract_access_rights_from_keys, tracking_copy::TrackingCopy, }; diff --git a/node/src/contract_core/tracking_copy/byte_size.rs b/node/src/components/contract_runtime/core/tracking_copy/byte_size.rs similarity index 97% rename from node/src/contract_core/tracking_copy/byte_size.rs rename to node/src/components/contract_runtime/core/tracking_copy/byte_size.rs index 8ac9f5f38b..7911ba9914 100644 --- a/node/src/contract_core/tracking_copy/byte_size.rs +++ b/node/src/components/contract_runtime/core/tracking_copy/byte_size.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeMap, mem}; -use crate::contract_shared::{account::Account, stored_value::StoredValue}; +use crate::components::contract_runtime::shared::{account::Account, stored_value::StoredValue}; use types::{bytesrepr::ToBytes, ContractWasm, Key}; /// Returns byte size of the element - both heap size and stack size. diff --git a/node/src/contract_core/tracking_copy/ext.rs b/node/src/components/contract_runtime/core/tracking_copy/ext.rs similarity index 96% rename from node/src/contract_core/tracking_copy/ext.rs rename to node/src/components/contract_runtime/core/tracking_copy/ext.rs index e82537ee03..6fc6365059 100644 --- a/node/src/contract_core/tracking_copy/ext.rs +++ b/node/src/components/contract_runtime/core/tracking_copy/ext.rs @@ -1,17 +1,17 @@ use std::convert::TryInto; -use crate::contract_shared::wasm_prep::{self, Preprocessor}; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::wasm_prep::{self, Preprocessor}; +use crate::components::contract_runtime::shared::{ account::Account, motes::Motes, newtypes::CorrelationId, stored_value::StoredValue, wasm, TypeMismatch, }; -use crate::contract_storage::global_state::StateReader; +use crate::components::contract_runtime::storage::global_state::StateReader; use types::{ account::AccountHash, CLValue, Contract, ContractHash, ContractPackage, ContractPackageHash, ContractWasm, ContractWasmHash, Key, U512, }; -use crate::contract_core::{execution, tracking_copy::TrackingCopy}; +use crate::components::contract_runtime::core::{execution, tracking_copy::TrackingCopy}; use parity_wasm::elements::Module; pub trait TrackingCopyExt { diff --git a/node/src/contract_core/tracking_copy/meter.rs b/node/src/components/contract_runtime/core/tracking_copy/meter.rs similarity index 86% rename from node/src/contract_core/tracking_copy/meter.rs rename to node/src/components/contract_runtime/core/tracking_copy/meter.rs index 37b550c564..a46a705e71 100644 --- a/node/src/contract_core/tracking_copy/meter.rs +++ b/node/src/components/contract_runtime/core/tracking_copy/meter.rs @@ -4,7 +4,7 @@ pub trait Meter { } pub mod heap_meter { - use crate::contract_core::tracking_copy::byte_size::ByteSize; + use crate::components::contract_runtime::core::tracking_copy::byte_size::ByteSize; pub struct HeapSize; diff --git a/node/src/contract_core/tracking_copy/mod.rs b/node/src/components/contract_runtime/core/tracking_copy/mod.rs similarity index 98% rename from node/src/contract_core/tracking_copy/mod.rs rename to node/src/components/contract_runtime/core/tracking_copy/mod.rs index 7895c9b9e0..5d353157ea 100644 --- a/node/src/contract_core/tracking_copy/mod.rs +++ b/node/src/components/contract_runtime/core/tracking_copy/mod.rs @@ -12,17 +12,19 @@ use std::{ use linked_hash_map::LinkedHashMap; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ additive_map::AdditiveMap, newtypes::CorrelationId, stored_value::StoredValue, transform::{self, Transform}, TypeMismatch, }; -use crate::contract_storage::global_state::StateReader; +use crate::components::contract_runtime::storage::global_state::StateReader; use types::{bytesrepr, CLType, CLValueError, Key}; -use crate::contract_core::engine_state::{execution_effect::ExecutionEffect, op::Op}; +use crate::components::contract_runtime::core::engine_state::{ + execution_effect::ExecutionEffect, op::Op, +}; pub use self::ext::TrackingCopyExt; use self::meter::{heap_meter::HeapSize, Meter}; diff --git a/node/src/contract_core/tracking_copy/tests.rs b/node/src/components/contract_runtime/core/tracking_copy/tests.rs similarity index 99% rename from node/src/contract_core/tracking_copy/tests.rs rename to node/src/components/contract_runtime/core/tracking_copy/tests.rs index f046357091..e6971a42f5 100644 --- a/node/src/contract_core/tracking_copy/tests.rs +++ b/node/src/components/contract_runtime/core/tracking_copy/tests.rs @@ -3,13 +3,13 @@ use std::{cell::Cell, iter, rc::Rc}; use assert_matches::assert_matches; use proptest::prelude::*; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ account::{Account, AssociatedKeys}, newtypes::CorrelationId, stored_value::{gens::stored_value_arb, StoredValue}, transform::Transform, }; -use crate::contract_storage::global_state::{ +use crate::components::contract_runtime::storage::global_state::{ in_memory::InMemoryGlobalState, StateProvider, StateReader, }; use types::{ @@ -22,7 +22,7 @@ use types::{ use super::{ meter::count_meter::Count, AddResult, TrackingCopy, TrackingCopyCache, TrackingCopyQueryResult, }; -use crate::contract_core::engine_state::op::Op; +use crate::components::contract_runtime::core::engine_state::op::Op; struct CountingDb { count: Rc>, diff --git a/node/src/contract_shared.rs b/node/src/components/contract_runtime/shared.rs similarity index 100% rename from node/src/contract_shared.rs rename to node/src/components/contract_runtime/shared.rs diff --git a/node/src/contract_shared/account.rs b/node/src/components/contract_runtime/shared/account.rs similarity index 99% rename from node/src/contract_shared/account.rs rename to node/src/components/contract_runtime/shared/account.rs index ae3bb113df..c4a159e9aa 100644 --- a/node/src/contract_shared/account.rs +++ b/node/src/components/contract_runtime/shared/account.rs @@ -257,7 +257,7 @@ pub mod gens { }; use super::*; - use crate::contract_shared::account::{ + use crate::components::contract_runtime::shared::account::{ action_thresholds::gens::action_thresholds_arb, associated_keys::gens::associated_keys_arb, }; diff --git a/node/src/contract_shared/account/action_thresholds.rs b/node/src/components/contract_runtime/shared/account/action_thresholds.rs similarity index 100% rename from node/src/contract_shared/account/action_thresholds.rs rename to node/src/components/contract_runtime/shared/account/action_thresholds.rs diff --git a/node/src/contract_shared/account/associated_keys.rs b/node/src/components/contract_runtime/shared/account/associated_keys.rs similarity index 100% rename from node/src/contract_shared/account/associated_keys.rs rename to node/src/components/contract_runtime/shared/account/associated_keys.rs diff --git a/node/src/contract_shared/additive_map.rs b/node/src/components/contract_runtime/shared/additive_map.rs similarity index 98% rename from node/src/contract_shared/additive_map.rs rename to node/src/components/contract_runtime/shared/additive_map.rs index b761e4bcd8..4c872525ac 100644 --- a/node/src/contract_shared/additive_map.rs +++ b/node/src/components/contract_runtime/shared/additive_map.rs @@ -149,7 +149,7 @@ impl Debug for AdditiveMap Visitor<'kvs> for MessageProperties { #[cfg(test)] mod tests { use super::*; - use crate::contract_shared::logging::DEFAULT_MESSAGE_KEY; + use crate::components::contract_runtime::shared::logging::DEFAULT_MESSAGE_KEY; #[test] fn should_get_process_id() { diff --git a/node/src/contract_shared/logging/terminal_logger.rs b/node/src/components/contract_runtime/shared/logging/terminal_logger.rs similarity index 98% rename from node/src/contract_shared/logging/terminal_logger.rs rename to node/src/components/contract_runtime/shared/logging/terminal_logger.rs index dc2b43e853..4a1ee3f4ad 100644 --- a/node/src/contract_shared/logging/terminal_logger.rs +++ b/node/src/components/contract_runtime/shared/logging/terminal_logger.rs @@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use log::{Level, LevelFilter, Log, Metadata, Record}; -use crate::contract_shared::logging::{ +use crate::components::contract_runtime::shared::logging::{ structured_message::{MessageId, MessageProperties, StructuredMessage, TimestampRfc3999}, Settings, Style, CASPERLABS_METADATA_TARGET, DEFAULT_MESSAGE_KEY, METRIC_METADATA_TARGET, }; diff --git a/node/src/contract_shared/motes.rs b/node/src/components/contract_runtime/shared/motes.rs similarity index 97% rename from node/src/contract_shared/motes.rs rename to node/src/components/contract_runtime/shared/motes.rs index 0f29f58cca..32bfe1cc13 100644 --- a/node/src/contract_shared/motes.rs +++ b/node/src/components/contract_runtime/shared/motes.rs @@ -4,7 +4,7 @@ use num::Zero; use types::U512; -use crate::contract_shared::gas::Gas; +use crate::components::contract_runtime::shared::gas::Gas; #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub struct Motes(U512); @@ -85,7 +85,7 @@ impl Zero for Motes { mod tests { use types::U512; - use crate::contract_shared::{gas::Gas, motes::Motes}; + use crate::components::contract_runtime::shared::{gas::Gas, motes::Motes}; #[test] fn should_be_able_to_get_instance_of_motes() { diff --git a/node/src/contract_shared/newtypes/macros.rs b/node/src/components/contract_runtime/shared/newtypes/macros.rs similarity index 100% rename from node/src/contract_shared/newtypes/macros.rs rename to node/src/components/contract_runtime/shared/newtypes/macros.rs diff --git a/node/src/contract_shared/newtypes/mod.rs b/node/src/components/contract_runtime/shared/newtypes/mod.rs similarity index 99% rename from node/src/contract_shared/newtypes/mod.rs rename to node/src/components/contract_runtime/shared/newtypes/mod.rs index 9ee90bd2fd..dbcf0e1b67 100644 --- a/node/src/contract_shared/newtypes/mod.rs +++ b/node/src/components/contract_runtime/shared/newtypes/mod.rs @@ -132,7 +132,7 @@ impl fmt::Display for CorrelationId { #[cfg(test)] mod tests { - use crate::contract_shared::{ + use crate::components::contract_runtime::shared::{ newtypes::{Blake2bHash, CorrelationId}, utils, }; diff --git a/node/src/contract_shared/page_size.rs b/node/src/components/contract_runtime/shared/page_size.rs similarity index 100% rename from node/src/contract_shared/page_size.rs rename to node/src/components/contract_runtime/shared/page_size.rs diff --git a/node/src/contract_shared/socket.rs b/node/src/components/contract_runtime/shared/socket.rs similarity index 100% rename from node/src/contract_shared/socket.rs rename to node/src/components/contract_runtime/shared/socket.rs diff --git a/node/src/contract_shared/stored_value.rs b/node/src/components/contract_runtime/shared/stored_value.rs similarity index 97% rename from node/src/contract_shared/stored_value.rs rename to node/src/components/contract_runtime/shared/stored_value.rs index 99849f9f63..6213556d22 100644 --- a/node/src/contract_shared/stored_value.rs +++ b/node/src/components/contract_runtime/shared/stored_value.rs @@ -6,7 +6,7 @@ use types::{ CLValue, Contract, ContractWasm, }; -use crate::contract_shared::{account::Account, TypeMismatch}; +use crate::components::contract_runtime::shared::{account::Account, TypeMismatch}; #[repr(u8)] enum Tag { @@ -208,7 +208,7 @@ pub mod gens { use types::gens::cl_value_arb; use super::StoredValue; - use crate::contract_shared::account::gens::account_arb; + use crate::components::contract_runtime::shared::account::gens::account_arb; use types::gens::{contract_arb, contract_package_arb, contract_wasm_arb}; pub fn stored_value_arb() -> impl Strategy { diff --git a/node/src/contract_shared/test_utils.rs b/node/src/components/contract_runtime/shared/test_utils.rs similarity index 86% rename from node/src/contract_shared/test_utils.rs rename to node/src/components/contract_runtime/shared/test_utils.rs index 1f6f2a99d3..c3359745cc 100644 --- a/node/src/contract_shared/test_utils.rs +++ b/node/src/components/contract_runtime/shared/test_utils.rs @@ -2,8 +2,8 @@ use types::{account::AccountHash, contracts::NamedKeys, AccessRights, Key, URef}; -use crate::contract_shared::wasm_costs::WasmCosts; -use crate::contract_shared::{account::Account, stored_value::StoredValue}; +use crate::components::contract_runtime::shared::wasm_costs::WasmCosts; +use crate::components::contract_runtime::shared::{account::Account, stored_value::StoredValue}; /// Returns an account value paired with its key pub fn mocked_account(account_hash: AccountHash) -> Vec<(Key, StoredValue)> { diff --git a/node/src/contract_shared/transform.rs b/node/src/components/contract_runtime/shared/transform.rs similarity index 98% rename from node/src/contract_shared/transform.rs rename to node/src/components/contract_runtime/shared/transform.rs index c2cf4648de..c07814290e 100644 --- a/node/src/contract_shared/transform.rs +++ b/node/src/components/contract_runtime/shared/transform.rs @@ -14,7 +14,7 @@ use types::{ CLType, CLTyped, CLValue, CLValueError, U128, U256, U512, }; -use crate::contract_shared::{stored_value::StoredValue, TypeMismatch}; +use crate::components::contract_runtime::shared::{stored_value::StoredValue, TypeMismatch}; /// Error type for applying and combining transforms. A `TypeMismatch` /// occurs when a transform cannot be applied because the types are @@ -283,7 +283,7 @@ pub mod gens { use proptest::{collection::vec, prelude::*}; use super::Transform; - use crate::contract_shared::stored_value::gens::stored_value_arb; + use crate::components::contract_runtime::shared::stored_value::gens::stored_value_arb; pub fn transform_arb() -> impl Strategy { prop_oneof![ @@ -313,7 +313,9 @@ mod tests { use types::{account::AccountHash, AccessRights, ContractWasm, Key, URef, U128, U256, U512}; use super::*; - use crate::contract_shared::account::{Account, ActionThresholds, AssociatedKeys}; + use crate::components::contract_runtime::shared::account::{ + Account, ActionThresholds, AssociatedKeys, + }; use std::collections::BTreeMap; const ZERO_ARRAY: [u8; 32] = [0; 32]; diff --git a/node/src/contract_shared/type_mismatch.rs b/node/src/components/contract_runtime/shared/type_mismatch.rs similarity index 100% rename from node/src/contract_shared/type_mismatch.rs rename to node/src/components/contract_runtime/shared/type_mismatch.rs diff --git a/node/src/contract_shared/utils.rs b/node/src/components/contract_runtime/shared/utils.rs similarity index 100% rename from node/src/contract_shared/utils.rs rename to node/src/components/contract_runtime/shared/utils.rs diff --git a/node/src/contract_shared/wasm.rs b/node/src/components/contract_runtime/shared/wasm.rs similarity index 89% rename from node/src/contract_shared/wasm.rs rename to node/src/components/contract_runtime/shared/wasm.rs index 8ad57d9e44..a756281cb4 100644 --- a/node/src/contract_shared/wasm.rs +++ b/node/src/components/contract_runtime/shared/wasm.rs @@ -1,6 +1,6 @@ use parity_wasm::elements::Module; -use crate::contract_shared::wasm_prep::{PreprocessingError, Preprocessor}; +use crate::components::contract_runtime::shared::wasm_prep::{PreprocessingError, Preprocessor}; static DO_NOTHING: &str = r#" (module diff --git a/node/src/contract_shared/wasm_costs.rs b/node/src/components/contract_runtime/shared/wasm_costs.rs similarity index 97% rename from node/src/contract_shared/wasm_costs.rs rename to node/src/components/contract_runtime/shared/wasm_costs.rs index d6163d33b9..93cf6e1b63 100644 --- a/node/src/contract_shared/wasm_costs.rs +++ b/node/src/components/contract_runtime/shared/wasm_costs.rs @@ -104,7 +104,7 @@ impl FromBytes for WasmCosts { pub mod gens { use proptest::{num, prop_compose}; - use crate::contract_shared::wasm_costs::WasmCosts; + use crate::components::contract_runtime::shared::wasm_costs::WasmCosts; prop_compose! { pub fn wasm_costs_arb()( @@ -142,7 +142,7 @@ mod tests { use types::bytesrepr; use super::gens; - use crate::contract_shared::wasm_costs::WasmCosts; + use crate::components::contract_runtime::shared::wasm_costs::WasmCosts; fn wasm_costs_mock() -> WasmCosts { WasmCosts { diff --git a/node/src/contract_shared/wasm_prep.rs b/node/src/components/contract_runtime/shared/wasm_prep.rs similarity index 96% rename from node/src/contract_shared/wasm_prep.rs rename to node/src/components/contract_runtime/shared/wasm_prep.rs index 74fab10f1f..1b245736c1 100644 --- a/node/src/contract_shared/wasm_prep.rs +++ b/node/src/components/contract_runtime/shared/wasm_prep.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Display, Formatter}; use parity_wasm::elements::{self, Module}; use pwasm_utils::{self, stack_height}; -use crate::contract_shared::wasm_costs::WasmCosts; +use crate::components::contract_runtime::shared::wasm_costs::WasmCosts; //NOTE: size of Wasm memory page is 64 KiB pub const MEM_PAGES: u32 = 64; diff --git a/node/src/contract_storage.rs b/node/src/components/contract_runtime/storage.rs similarity index 85% rename from node/src/contract_storage.rs rename to node/src/components/contract_runtime/storage.rs index fe450ba078..803a6f5deb 100644 --- a/node/src/contract_storage.rs +++ b/node/src/components/contract_runtime/storage.rs @@ -22,7 +22,7 @@ lazy_static! { // page size on x86_64 linux = 4096 bytes // 52428800 / 4096 = 12800 static ref TEST_MAP_SIZE: usize = { - let page_size = *crate::contract_shared::page_size::PAGE_SIZE; + let page_size = *crate::components::contract_runtime::shared::page_size::PAGE_SIZE; page_size * 12800 }; } diff --git a/node/src/contract_storage/error/in_memory.rs b/node/src/components/contract_runtime/storage/error/in_memory.rs similarity index 100% rename from node/src/contract_storage/error/in_memory.rs rename to node/src/components/contract_runtime/storage/error/in_memory.rs diff --git a/node/src/contract_storage/error/lmdb.rs b/node/src/components/contract_runtime/storage/error/lmdb.rs similarity index 100% rename from node/src/contract_storage/error/lmdb.rs rename to node/src/components/contract_runtime/storage/error/lmdb.rs diff --git a/node/src/contract_storage/error/mod.rs b/node/src/components/contract_runtime/storage/error/mod.rs similarity index 100% rename from node/src/contract_storage/error/mod.rs rename to node/src/components/contract_runtime/storage/error/mod.rs diff --git a/node/src/contract_storage/global_state/in_memory.rs b/node/src/components/contract_runtime/storage/global_state/in_memory.rs similarity index 99% rename from node/src/contract_storage/global_state/in_memory.rs rename to node/src/components/contract_runtime/storage/global_state/in_memory.rs index 5e4e4d9783..92bc755199 100644 --- a/node/src/contract_storage/global_state/in_memory.rs +++ b/node/src/components/contract_runtime/storage/global_state/in_memory.rs @@ -1,6 +1,6 @@ use std::{ops::Deref, sync::Arc}; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ additive_map::AdditiveMap, newtypes::{Blake2bHash, CorrelationId}, stored_value::StoredValue, @@ -8,7 +8,7 @@ use crate::contract_shared::{ }; use types::{Key, ProtocolVersion}; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ error::{self, in_memory}, global_state::{commit, CommitResult, StateProvider, StateReader}, protocol_data::ProtocolData, diff --git a/node/src/contract_storage/global_state/lmdb.rs b/node/src/components/contract_runtime/storage/global_state/lmdb.rs similarity index 98% rename from node/src/contract_storage/global_state/lmdb.rs rename to node/src/components/contract_runtime/storage/global_state/lmdb.rs index 7f9ea9977f..1997d7cfb4 100644 --- a/node/src/contract_storage/global_state/lmdb.rs +++ b/node/src/components/contract_runtime/storage/global_state/lmdb.rs @@ -1,6 +1,6 @@ use std::{ops::Deref, sync::Arc}; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ additive_map::AdditiveMap, newtypes::{Blake2bHash, CorrelationId}, stored_value::StoredValue, @@ -8,7 +8,7 @@ use crate::contract_shared::{ }; use types::{Key, ProtocolVersion}; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ error, global_state::{commit, CommitResult, StateProvider, StateReader}, protocol_data::ProtocolData, @@ -166,7 +166,7 @@ mod tests { use types::{account::AccountHash, CLValue}; - use crate::contract_storage::{ + use crate::components::contract_runtime::storage::{ trie_store::operations::{write, WriteResult}, TEST_MAP_SIZE, }; diff --git a/node/src/contract_storage/global_state/mod.rs b/node/src/components/contract_runtime/storage/global_state/mod.rs similarity index 98% rename from node/src/contract_storage/global_state/mod.rs rename to node/src/components/contract_runtime/storage/global_state/mod.rs index a4741ce397..9cf8640d7a 100644 --- a/node/src/contract_storage/global_state/mod.rs +++ b/node/src/components/contract_runtime/storage/global_state/mod.rs @@ -3,7 +3,7 @@ pub mod lmdb; use std::{collections::HashMap, fmt, hash::BuildHasher, time::Instant}; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ additive_map::AdditiveMap, logging::{log_duration, log_metric}, newtypes::{Blake2bHash, CorrelationId}, @@ -13,7 +13,7 @@ use crate::contract_shared::{ }; use types::{account::AccountHash, bytesrepr, Key, ProtocolVersion, U512}; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ protocol_data::ProtocolData, transaction_source::{Transaction, TransactionSource}, trie::Trie, diff --git a/node/src/contract_storage/protocol_data.rs b/node/src/components/contract_runtime/storage/protocol_data.rs similarity index 97% rename from node/src/contract_storage/protocol_data.rs rename to node/src/components/contract_runtime/storage/protocol_data.rs index 7619618e92..6a85eecbe8 100644 --- a/node/src/contract_storage/protocol_data.rs +++ b/node/src/components/contract_runtime/storage/protocol_data.rs @@ -1,4 +1,6 @@ -use crate::contract_shared::wasm_costs::{WasmCosts, WASM_COSTS_SERIALIZED_LENGTH}; +use crate::components::contract_runtime::shared::wasm_costs::{ + WasmCosts, WASM_COSTS_SERIALIZED_LENGTH, +}; use std::collections::BTreeMap; use types::{ bytesrepr::{self, FromBytes, ToBytes}, @@ -161,7 +163,7 @@ impl FromBytes for ProtocolData { pub(crate) mod gens { use proptest::prop_compose; - use crate::contract_shared::wasm_costs::gens as wasm_costs_gens; + use crate::components::contract_runtime::shared::wasm_costs::gens as wasm_costs_gens; use types::gens; use super::ProtocolData; @@ -187,7 +189,7 @@ pub(crate) mod gens { mod tests { use proptest::proptest; - use crate::contract_shared::wasm_costs::WasmCosts; + use crate::components::contract_runtime::shared::wasm_costs::WasmCosts; use types::{bytesrepr, ContractHash}; use super::{gens, ProtocolData}; diff --git a/node/src/contract_storage/protocol_data_store/in_memory.rs b/node/src/components/contract_runtime/storage/protocol_data_store/in_memory.rs similarity index 94% rename from node/src/contract_storage/protocol_data_store/in_memory.rs rename to node/src/components/contract_runtime/storage/protocol_data_store/in_memory.rs index d2046a68f2..f6b1dbe538 100644 --- a/node/src/contract_storage/protocol_data_store/in_memory.rs +++ b/node/src/components/contract_runtime/storage/protocol_data_store/in_memory.rs @@ -1,6 +1,6 @@ use types::ProtocolVersion; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ error::in_memory::Error, protocol_data::ProtocolData, protocol_data_store::{self, ProtocolDataStore}, diff --git a/node/src/contract_storage/protocol_data_store/lmdb.rs b/node/src/components/contract_runtime/storage/protocol_data_store/lmdb.rs similarity index 96% rename from node/src/contract_storage/protocol_data_store/lmdb.rs rename to node/src/components/contract_runtime/storage/protocol_data_store/lmdb.rs index 0c9502ce26..013fc934ca 100644 --- a/node/src/contract_storage/protocol_data_store/lmdb.rs +++ b/node/src/components/contract_runtime/storage/protocol_data_store/lmdb.rs @@ -1,7 +1,7 @@ use lmdb::{Database, DatabaseFlags}; use types::ProtocolVersion; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ error, protocol_data::ProtocolData, protocol_data_store::{self, ProtocolDataStore}, diff --git a/node/src/contract_storage/protocol_data_store/mod.rs b/node/src/components/contract_runtime/storage/protocol_data_store/mod.rs similarity index 80% rename from node/src/contract_storage/protocol_data_store/mod.rs rename to node/src/components/contract_runtime/storage/protocol_data_store/mod.rs index ea25a90310..abbc3662c8 100644 --- a/node/src/contract_storage/protocol_data_store/mod.rs +++ b/node/src/components/contract_runtime/storage/protocol_data_store/mod.rs @@ -7,7 +7,7 @@ pub mod lmdb; #[cfg(test)] mod tests; -use crate::contract_storage::{protocol_data::ProtocolData, store::Store}; +use crate::components::contract_runtime::storage::{protocol_data::ProtocolData, store::Store}; const NAME: &str = "PROTOCOL_DATA_STORE"; diff --git a/node/src/contract_storage/protocol_data_store/tests/mod.rs b/node/src/components/contract_runtime/storage/protocol_data_store/tests/mod.rs similarity index 100% rename from node/src/contract_storage/protocol_data_store/tests/mod.rs rename to node/src/components/contract_runtime/storage/protocol_data_store/tests/mod.rs diff --git a/node/src/contract_storage/protocol_data_store/tests/proptests.rs b/node/src/components/contract_runtime/storage/protocol_data_store/tests/proptests.rs similarity index 97% rename from node/src/contract_storage/protocol_data_store/tests/proptests.rs rename to node/src/components/contract_runtime/storage/protocol_data_store/tests/proptests.rs index 7f98016d34..f61ab65536 100644 --- a/node/src/contract_storage/protocol_data_store/tests/proptests.rs +++ b/node/src/components/contract_runtime/storage/protocol_data_store/tests/proptests.rs @@ -5,7 +5,7 @@ use proptest::{collection, prelude::proptest}; use types::{gens as gens_ext, ProtocolVersion}; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ protocol_data::{gens, ProtocolData}, protocol_data_store::{in_memory::InMemoryProtocolDataStore, lmdb::LmdbProtocolDataStore}, store::tests as store_tests, diff --git a/node/src/contract_storage/store/mod.rs b/node/src/components/contract_runtime/storage/store/mod.rs similarity index 92% rename from node/src/contract_storage/store/mod.rs rename to node/src/components/contract_runtime/storage/store/mod.rs index b67c642644..9cde9a5d7f 100644 --- a/node/src/contract_storage/store/mod.rs +++ b/node/src/components/contract_runtime/storage/store/mod.rs @@ -5,7 +5,7 @@ pub(crate) mod tests; use types::bytesrepr::{self, FromBytes, ToBytes}; pub use self::store_ext::StoreExt; -use crate::contract_storage::transaction_source::{Readable, Writable}; +use crate::components::contract_runtime::storage::transaction_source::{Readable, Writable}; pub trait Store { type Error: From; diff --git a/node/src/contract_storage/store/store_ext.rs b/node/src/components/contract_runtime/storage/store/store_ext.rs similarity index 95% rename from node/src/contract_storage/store/store_ext.rs rename to node/src/components/contract_runtime/storage/store/store_ext.rs index 0a3e7c2ba7..55eed9920b 100644 --- a/node/src/contract_storage/store/store_ext.rs +++ b/node/src/components/contract_runtime/storage/store/store_ext.rs @@ -1,6 +1,6 @@ use types::bytesrepr::{FromBytes, ToBytes}; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ store::Store, transaction_source::{Readable, Writable}, }; diff --git a/node/src/contract_storage/store/tests.rs b/node/src/components/contract_runtime/storage/store/tests.rs similarity index 96% rename from node/src/contract_storage/store/tests.rs rename to node/src/components/contract_runtime/storage/store/tests.rs index 5eb983727c..33ad268e23 100644 --- a/node/src/contract_storage/store/tests.rs +++ b/node/src/components/contract_runtime/storage/store/tests.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use types::bytesrepr::{FromBytes, ToBytes}; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ store::{Store, StoreExt}, transaction_source::{Transaction, TransactionSource}, }; diff --git a/node/src/contract_storage/transaction_source/in_memory.rs b/node/src/components/contract_runtime/storage/transaction_source/in_memory.rs similarity index 98% rename from node/src/contract_storage/transaction_source/in_memory.rs rename to node/src/components/contract_runtime/storage/transaction_source/in_memory.rs index cd7ff48c09..bdef8051fc 100644 --- a/node/src/contract_storage/transaction_source/in_memory.rs +++ b/node/src/components/contract_runtime/storage/transaction_source/in_memory.rs @@ -3,7 +3,7 @@ use std::{ sync::{self, Arc, Mutex, MutexGuard}, }; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ error::in_memory::Error, transaction_source::{Readable, Transaction, TransactionSource, Writable}, }; diff --git a/node/src/contract_storage/transaction_source/lmdb.rs b/node/src/components/contract_runtime/storage/transaction_source/lmdb.rs similarity index 98% rename from node/src/contract_storage/transaction_source/lmdb.rs rename to node/src/components/contract_runtime/storage/transaction_source/lmdb.rs index 563587a6e6..b75157f483 100644 --- a/node/src/contract_storage/transaction_source/lmdb.rs +++ b/node/src/components/contract_runtime/storage/transaction_source/lmdb.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use lmdb::{self, Database, Environment, RoTransaction, RwTransaction, WriteFlags}; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ error, transaction_source::{Readable, Transaction, TransactionSource, Writable}, MAX_DBS, diff --git a/node/src/contract_storage/transaction_source/mod.rs b/node/src/components/contract_runtime/storage/transaction_source/mod.rs similarity index 100% rename from node/src/contract_storage/transaction_source/mod.rs rename to node/src/components/contract_runtime/storage/transaction_source/mod.rs diff --git a/node/src/contract_storage/trie/gens.rs b/node/src/components/contract_runtime/storage/trie/gens.rs similarity index 96% rename from node/src/contract_storage/trie/gens.rs rename to node/src/components/contract_runtime/storage/trie/gens.rs index a4f5c7aedc..e48b612fe2 100644 --- a/node/src/contract_storage/trie/gens.rs +++ b/node/src/components/contract_runtime/storage/trie/gens.rs @@ -1,6 +1,6 @@ use proptest::{collection::vec, option, prelude::*}; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ newtypes::Blake2bHash, stored_value::{gens::stored_value_arb, StoredValue}, }; diff --git a/node/src/contract_storage/trie/mod.rs b/node/src/components/contract_runtime/storage/trie/mod.rs similarity index 97% rename from node/src/contract_storage/trie/mod.rs rename to node/src/components/contract_runtime/storage/trie/mod.rs index b60a0bb4f1..d5873d43f6 100644 --- a/node/src/contract_storage/trie/mod.rs +++ b/node/src/components/contract_runtime/storage/trie/mod.rs @@ -1,6 +1,6 @@ //! Core types for a Merkle Trie -use crate::contract_shared::newtypes::{Blake2bHash, BLAKE2B_DIGEST_LENGTH}; +use crate::components::contract_runtime::shared::newtypes::{Blake2bHash, BLAKE2B_DIGEST_LENGTH}; use types::bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}; #[cfg(test)] @@ -311,8 +311,8 @@ impl FromBytes for Trie { } pub(crate) mod operations { - use crate::contract_shared::newtypes::Blake2bHash; - use crate::contract_storage::trie::Trie; + use crate::components::contract_runtime::shared::newtypes::Blake2bHash; + use crate::components::contract_runtime::storage::trie::Trie; use types::bytesrepr::{self, ToBytes}; /// Creates a tuple containing an empty root hash and an empty root (a node diff --git a/node/src/contract_storage/trie/tests.rs b/node/src/components/contract_runtime/storage/trie/tests.rs similarity index 90% rename from node/src/contract_storage/trie/tests.rs rename to node/src/components/contract_runtime/storage/trie/tests.rs index 4ab537e00e..5610cd6277 100644 --- a/node/src/contract_storage/trie/tests.rs +++ b/node/src/components/contract_runtime/storage/trie/tests.rs @@ -8,9 +8,9 @@ fn radix_is_256() { } mod pointer_block { - use crate::contract_shared::newtypes::Blake2bHash; + use crate::components::contract_runtime::shared::newtypes::Blake2bHash; - use crate::contract_storage::trie::*; + use crate::components::contract_runtime::storage::trie::*; /// A defense against changes to [`RADIX`](history::trie::RADIX). #[test] @@ -53,7 +53,7 @@ mod proptests { use types::bytesrepr; - use crate::contract_storage::trie::gens::*; + use crate::components::contract_runtime::storage::trie::gens::*; proptest! { #[test] diff --git a/node/src/contract_storage/trie_store/in_memory.rs b/node/src/components/contract_runtime/storage/trie_store/in_memory.rs similarity index 86% rename from node/src/contract_storage/trie_store/in_memory.rs rename to node/src/components/contract_runtime/storage/trie_store/in_memory.rs index 6bdfaf0deb..f50da25632 100644 --- a/node/src/contract_storage/trie_store/in_memory.rs +++ b/node/src/components/contract_runtime/storage/trie_store/in_memory.rs @@ -3,13 +3,13 @@ //! # Usage //! //! ``` -//! use casperlabs_node::contract_storage::store::Store; -//! use casperlabs_node::contract_storage::transaction_source::{Transaction, TransactionSource}; -//! use casperlabs_node::contract_storage::transaction_source::in_memory::InMemoryEnvironment; -//! use casperlabs_node::contract_storage::trie::{Pointer, PointerBlock, Trie}; -//! use casperlabs_node::contract_storage::trie_store::in_memory::InMemoryTrieStore; +//! use casperlabs_node::components::contract_runtime::storage::store::Store; +//! use casperlabs_node::components::contract_runtime::storage::transaction_source::{Transaction, TransactionSource}; +//! use casperlabs_node::components::contract_runtime::storage::transaction_source::in_memory::InMemoryEnvironment; +//! use casperlabs_node::components::contract_runtime::storage::trie::{Pointer, PointerBlock, Trie}; +//! use casperlabs_node::components::contract_runtime::storage::trie_store::in_memory::InMemoryTrieStore; //! use types::bytesrepr::ToBytes; -//! use casperlabs_node::contract_shared::newtypes::Blake2bHash; +//! use casperlabs_node::components::contract_runtime::shared::newtypes::Blake2bHash; //! //! // Create some leaves //! let leaf_1 = Trie::Leaf { key: vec![0u8, 0, 0], value: b"val_1".to_vec() }; @@ -98,7 +98,7 @@ //! ``` use super::{Blake2bHash, Store, Trie, TrieStore, NAME}; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ error::in_memory::Error, transaction_source::in_memory::InMemoryEnvironment, }; diff --git a/node/src/contract_storage/trie_store/lmdb.rs b/node/src/components/contract_runtime/storage/trie_store/lmdb.rs similarity index 87% rename from node/src/contract_storage/trie_store/lmdb.rs rename to node/src/components/contract_runtime/storage/trie_store/lmdb.rs index 3b0a161061..1f4827b919 100644 --- a/node/src/contract_storage/trie_store/lmdb.rs +++ b/node/src/components/contract_runtime/storage/trie_store/lmdb.rs @@ -3,12 +3,12 @@ //! # Usage //! //! ``` -//! use casperlabs_node::contract_storage::store::Store; -//! use casperlabs_node::contract_storage::transaction_source::{Transaction, TransactionSource}; -//! use casperlabs_node::contract_storage::transaction_source::lmdb::LmdbEnvironment; -//! use casperlabs_node::contract_storage::trie::{Pointer, PointerBlock, Trie}; -//! use casperlabs_node::contract_storage::trie_store::lmdb::LmdbTrieStore; -//! use casperlabs_node::contract_shared::newtypes::Blake2bHash; +//! use casperlabs_node::components::contract_runtime::storage::store::Store; +//! use casperlabs_node::components::contract_runtime::storage::transaction_source::{Transaction, TransactionSource}; +//! use casperlabs_node::components::contract_runtime::storage::transaction_source::lmdb::LmdbEnvironment; +//! use casperlabs_node::components::contract_runtime::storage::trie::{Pointer, PointerBlock, Trie}; +//! use casperlabs_node::components::contract_runtime::storage::trie_store::lmdb::LmdbTrieStore; +//! use casperlabs_node::components::contract_runtime::shared::newtypes::Blake2bHash; //! use types::bytesrepr::ToBytes; //! use lmdb::DatabaseFlags; //! use tempfile::tempdir; @@ -105,9 +105,9 @@ use lmdb::{Database, DatabaseFlags}; -use crate::contract_shared::newtypes::Blake2bHash; +use crate::components::contract_runtime::shared::newtypes::Blake2bHash; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ error, store::Store, transaction_source::lmdb::LmdbEnvironment, diff --git a/node/src/contract_storage/trie_store/mod.rs b/node/src/components/contract_runtime/storage/trie_store/mod.rs similarity index 74% rename from node/src/contract_storage/trie_store/mod.rs rename to node/src/components/contract_runtime/storage/trie_store/mod.rs index ba1cf1dc25..9cedd0cc11 100644 --- a/node/src/contract_storage/trie_store/mod.rs +++ b/node/src/components/contract_runtime/storage/trie_store/mod.rs @@ -8,9 +8,9 @@ pub(crate) mod operations; #[cfg(test)] mod tests; -use crate::contract_shared::newtypes::Blake2bHash; +use crate::components::contract_runtime::shared::newtypes::Blake2bHash; -use crate::contract_storage::{store::Store, trie::Trie}; +use crate::components::contract_runtime::storage::{store::Store, trie::Trie}; const NAME: &str = "TRIE_STORE"; diff --git a/node/src/contract_storage/trie_store/operations/mod.rs b/node/src/components/contract_runtime/storage/trie_store/operations/mod.rs similarity index 99% rename from node/src/contract_storage/trie_store/operations/mod.rs rename to node/src/components/contract_runtime/storage/trie_store/operations/mod.rs index 41d531b63b..5b1ddfb87b 100644 --- a/node/src/contract_storage/trie_store/operations/mod.rs +++ b/node/src/components/contract_runtime/storage/trie_store/operations/mod.rs @@ -3,13 +3,13 @@ mod tests; use std::{cmp, collections::VecDeque, mem, time::Instant}; -use crate::contract_shared::{ +use crate::components::contract_runtime::shared::{ logging::{log_duration, log_metric}, newtypes::{Blake2bHash, CorrelationId}, }; use types::bytesrepr::{self, FromBytes, ToBytes}; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ transaction_source::{Readable, Writable}, trie::{Parents, Pointer, Trie, RADIX}, trie_store::TrieStore, diff --git a/node/src/contract_storage/trie_store/operations/tests/ee_699.rs b/node/src/components/contract_runtime/storage/trie_store/operations/tests/ee_699.rs similarity index 99% rename from node/src/contract_storage/trie_store/operations/tests/ee_699.rs rename to node/src/components/contract_runtime/storage/trie_store/operations/tests/ee_699.rs index b60f39143c..dca8aa7fd3 100644 --- a/node/src/contract_storage/trie_store/operations/tests/ee_699.rs +++ b/node/src/components/contract_runtime/storage/trie_store/operations/tests/ee_699.rs @@ -1,6 +1,6 @@ use proptest::{arbitrary, array, collection, prop_oneof, strategy::Strategy}; -use crate::contract_shared::{make_array_newtype, newtypes::Blake2bHash}; +use crate::components::contract_runtime::shared::{make_array_newtype, newtypes::Blake2bHash}; use types::{ bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, gens, URef, diff --git a/node/src/contract_storage/trie_store/operations/tests/keys.rs b/node/src/components/contract_runtime/storage/trie_store/operations/tests/keys.rs similarity index 94% rename from node/src/contract_storage/trie_store/operations/tests/keys.rs rename to node/src/components/contract_runtime/storage/trie_store/operations/tests/keys.rs index f9b0c8ef07..6679d898c8 100644 --- a/node/src/contract_storage/trie_store/operations/tests/keys.rs +++ b/node/src/components/contract_runtime/storage/trie_store/operations/tests/keys.rs @@ -1,7 +1,7 @@ mod partial_tries { - use crate::contract_shared::newtypes::CorrelationId; + use crate::components::contract_runtime::shared::newtypes::CorrelationId; - use crate::contract_storage::{ + use crate::components::contract_runtime::storage::{ transaction_source::{Transaction, TransactionSource}, trie::Trie, trie_store::operations::{ @@ -87,9 +87,9 @@ mod partial_tries { } mod full_tries { - use crate::contract_shared::newtypes::{Blake2bHash, CorrelationId}; + use crate::components::contract_runtime::shared::newtypes::{Blake2bHash, CorrelationId}; - use crate::contract_storage::{ + use crate::components::contract_runtime::storage::{ transaction_source::{Transaction, TransactionSource}, trie::Trie, trie_store::operations::{ @@ -147,10 +147,10 @@ mod full_tries { #[cfg(debug_assertions)] mod keys_iterator { - use crate::contract_shared::newtypes::{Blake2bHash, CorrelationId}; + use crate::components::contract_runtime::shared::newtypes::{Blake2bHash, CorrelationId}; use types::bytesrepr; - use crate::contract_storage::{ + use crate::components::contract_runtime::storage::{ transaction_source::TransactionSource, trie::{Pointer, Trie}, trie_store::operations::{ @@ -247,9 +247,9 @@ mod keys_iterator { } mod keys_with_prefix_iterator { - use crate::contract_shared::newtypes::CorrelationId; + use crate::components::contract_runtime::shared::newtypes::CorrelationId; - use crate::contract_storage::{ + use crate::components::contract_runtime::storage::{ transaction_source::TransactionSource, trie::Trie, trie_store::operations::{ diff --git a/node/src/contract_storage/trie_store/operations/tests/mod.rs b/node/src/components/contract_runtime/storage/trie_store/operations/tests/mod.rs similarity index 99% rename from node/src/contract_storage/trie_store/operations/tests/mod.rs rename to node/src/components/contract_runtime/storage/trie_store/operations/tests/mod.rs index 42045bdb71..404db8f6ed 100644 --- a/node/src/contract_storage/trie_store/operations/tests/mod.rs +++ b/node/src/components/contract_runtime/storage/trie_store/operations/tests/mod.rs @@ -9,10 +9,10 @@ use std::{collections::HashMap, convert}; use lmdb::DatabaseFlags; use tempfile::{tempdir, TempDir}; -use crate::contract_shared::newtypes::{Blake2bHash, CorrelationId}; +use crate::components::contract_runtime::shared::newtypes::{Blake2bHash, CorrelationId}; use types::bytesrepr::{self, FromBytes, ToBytes}; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ error::{self, in_memory}, transaction_source::{ in_memory::InMemoryEnvironment, lmdb::LmdbEnvironment, Readable, Transaction, diff --git a/node/src/contract_storage/trie_store/operations/tests/proptests.rs b/node/src/components/contract_runtime/storage/trie_store/operations/tests/proptests.rs similarity index 100% rename from node/src/contract_storage/trie_store/operations/tests/proptests.rs rename to node/src/components/contract_runtime/storage/trie_store/operations/tests/proptests.rs diff --git a/node/src/contract_storage/trie_store/operations/tests/read.rs b/node/src/components/contract_runtime/storage/trie_store/operations/tests/read.rs similarity index 98% rename from node/src/contract_storage/trie_store/operations/tests/read.rs rename to node/src/components/contract_runtime/storage/trie_store/operations/tests/read.rs index 44e84751d8..e0fd0d40e7 100644 --- a/node/src/contract_storage/trie_store/operations/tests/read.rs +++ b/node/src/components/contract_runtime/storage/trie_store/operations/tests/read.rs @@ -9,7 +9,7 @@ //! [`full_tries`] modules for more info. use super::*; -use crate::contract_storage::error::{self, in_memory}; +use crate::components::contract_runtime::storage::error::{self, in_memory}; mod partial_tries { //! Here we construct 6 separate "partial" tries, increasing in size diff --git a/node/src/contract_storage/trie_store/operations/tests/scan.rs b/node/src/components/contract_runtime/storage/trie_store/operations/tests/scan.rs similarity index 97% rename from node/src/contract_storage/trie_store/operations/tests/scan.rs rename to node/src/components/contract_runtime/storage/trie_store/operations/tests/scan.rs index 4387ce50cf..07dd12b994 100644 --- a/node/src/contract_storage/trie_store/operations/tests/scan.rs +++ b/node/src/components/contract_runtime/storage/trie_store/operations/tests/scan.rs @@ -1,7 +1,7 @@ -use crate::contract_shared::newtypes::Blake2bHash; +use crate::components::contract_runtime::shared::newtypes::Blake2bHash; use super::*; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ error::{self, in_memory}, trie_store::operations::{scan, TrieScan}, }; diff --git a/node/src/contract_storage/trie_store/operations/tests/write.rs b/node/src/components/contract_runtime/storage/trie_store/operations/tests/write.rs similarity index 100% rename from node/src/contract_storage/trie_store/operations/tests/write.rs rename to node/src/components/contract_runtime/storage/trie_store/operations/tests/write.rs diff --git a/node/src/contract_storage/trie_store/tests/concurrent.rs b/node/src/components/contract_runtime/storage/trie_store/tests/concurrent.rs similarity index 98% rename from node/src/contract_storage/trie_store/tests/concurrent.rs rename to node/src/components/contract_runtime/storage/trie_store/tests/concurrent.rs index 24becf0a84..73eb423fb3 100644 --- a/node/src/contract_storage/trie_store/tests/concurrent.rs +++ b/node/src/components/contract_runtime/storage/trie_store/tests/concurrent.rs @@ -6,7 +6,7 @@ use std::{ use tempfile::tempdir; use super::TestData; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ store::Store, transaction_source::{ in_memory::InMemoryEnvironment, lmdb::LmdbEnvironment, Transaction, TransactionSource, diff --git a/node/src/contract_storage/trie_store/tests/mod.rs b/node/src/components/contract_runtime/storage/trie_store/tests/mod.rs similarity index 93% rename from node/src/contract_storage/trie_store/tests/mod.rs rename to node/src/components/contract_runtime/storage/trie_store/tests/mod.rs index ab0ac380f9..1251bfd8a9 100644 --- a/node/src/contract_storage/trie_store/tests/mod.rs +++ b/node/src/components/contract_runtime/storage/trie_store/tests/mod.rs @@ -2,10 +2,10 @@ mod concurrent; mod proptests; mod simple; -use crate::contract_shared::newtypes::Blake2bHash; +use crate::components::contract_runtime::shared::newtypes::Blake2bHash; use types::bytesrepr::ToBytes; -use crate::contract_storage::trie::{Pointer, PointerBlock, Trie}; +use crate::components::contract_runtime::storage::trie::{Pointer, PointerBlock, Trie}; #[derive(Clone)] struct TestData(Blake2bHash, Trie); diff --git a/node/src/contract_storage/trie_store/tests/proptests.rs b/node/src/components/contract_runtime/storage/trie_store/tests/proptests.rs similarity index 89% rename from node/src/contract_storage/trie_store/tests/proptests.rs rename to node/src/components/contract_runtime/storage/trie_store/tests/proptests.rs index fc518eba3c..cc904bdbc2 100644 --- a/node/src/contract_storage/trie_store/tests/proptests.rs +++ b/node/src/components/contract_runtime/storage/trie_store/tests/proptests.rs @@ -4,10 +4,12 @@ use lmdb::DatabaseFlags; use proptest::{collection::vec, prelude::proptest}; use tempfile::tempdir; -use crate::contract_shared::{newtypes::Blake2bHash, stored_value::StoredValue}; +use crate::components::contract_runtime::shared::{ + newtypes::Blake2bHash, stored_value::StoredValue, +}; use types::{bytesrepr::ToBytes, Key}; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ store::tests as store_tests, trie::{gens::trie_arb, Trie}, TEST_MAP_SIZE, @@ -28,7 +30,7 @@ fn get_range() -> RangeInclusive { } fn in_memory_roundtrip_succeeds(inputs: Vec>) -> bool { - use crate::contract_storage::{ + use crate::components::contract_runtime::storage::{ transaction_source::in_memory::InMemoryEnvironment, trie_store::in_memory::InMemoryTrieStore, }; @@ -45,7 +47,7 @@ fn in_memory_roundtrip_succeeds(inputs: Vec>) -> bool { } fn lmdb_roundtrip_succeeds(inputs: Vec>) -> bool { - use crate::contract_storage::{ + use crate::components::contract_runtime::storage::{ transaction_source::lmdb::LmdbEnvironment, trie_store::lmdb::LmdbTrieStore, }; diff --git a/node/src/contract_storage/trie_store/tests/simple.rs b/node/src/components/contract_runtime/storage/trie_store/tests/simple.rs similarity index 99% rename from node/src/contract_storage/trie_store/tests/simple.rs rename to node/src/components/contract_runtime/storage/trie_store/tests/simple.rs index b56989bfae..75b22618da 100644 --- a/node/src/contract_storage/trie_store/tests/simple.rs +++ b/node/src/components/contract_runtime/storage/trie_store/tests/simple.rs @@ -4,7 +4,7 @@ use tempfile::tempdir; use types::bytesrepr::{FromBytes, ToBytes}; use super::TestData; -use crate::contract_storage::{ +use crate::components::contract_runtime::storage::{ error::{self, in_memory}, store::StoreExt, transaction_source::{ diff --git a/node/src/components/storage/config.rs b/node/src/components/storage/config.rs index 72493eb4be..7d990df397 100644 --- a/node/src/components/storage/config.rs +++ b/node/src/components/storage/config.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use crate::contract_shared::page_size; +use crate::components::contract_runtime::shared::page_size; use directories::ProjectDirs; use serde::{Deserialize, Serialize}; use tempfile::TempDir; diff --git a/node/src/lib.rs b/node/src/lib.rs index 4a95eb8f27..88709b16bc 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -25,9 +25,6 @@ #![allow(clippy::type_complexity)] pub mod components; -pub mod contract_core; -pub mod contract_shared; -pub mod contract_storage; pub mod crypto; pub mod effect; pub mod logging; diff --git a/smart-contracts/contract/Cargo.toml b/smart_contracts/contract/Cargo.toml similarity index 100% rename from smart-contracts/contract/Cargo.toml rename to smart_contracts/contract/Cargo.toml diff --git a/smart-contracts/contract/README.md b/smart_contracts/contract/README.md similarity index 100% rename from smart-contracts/contract/README.md rename to smart_contracts/contract/README.md diff --git a/smart-contracts/contract/src/contract_api/account.rs b/smart_contracts/contract/src/contract_api/account.rs similarity index 100% rename from smart-contracts/contract/src/contract_api/account.rs rename to smart_contracts/contract/src/contract_api/account.rs diff --git a/smart-contracts/contract/src/contract_api/mod.rs b/smart_contracts/contract/src/contract_api/mod.rs similarity index 100% rename from smart-contracts/contract/src/contract_api/mod.rs rename to smart_contracts/contract/src/contract_api/mod.rs diff --git a/smart-contracts/contract/src/contract_api/runtime.rs b/smart_contracts/contract/src/contract_api/runtime.rs similarity index 100% rename from smart-contracts/contract/src/contract_api/runtime.rs rename to smart_contracts/contract/src/contract_api/runtime.rs diff --git a/smart-contracts/contract/src/contract_api/storage.rs b/smart_contracts/contract/src/contract_api/storage.rs similarity index 100% rename from smart-contracts/contract/src/contract_api/storage.rs rename to smart_contracts/contract/src/contract_api/storage.rs diff --git a/smart-contracts/contract/src/contract_api/system.rs b/smart_contracts/contract/src/contract_api/system.rs similarity index 100% rename from smart-contracts/contract/src/contract_api/system.rs rename to smart_contracts/contract/src/contract_api/system.rs diff --git a/smart-contracts/contract/src/ext_ffi.rs b/smart_contracts/contract/src/ext_ffi.rs similarity index 100% rename from smart-contracts/contract/src/ext_ffi.rs rename to smart_contracts/contract/src/ext_ffi.rs diff --git a/smart-contracts/contract/src/handlers.rs b/smart_contracts/contract/src/handlers.rs similarity index 100% rename from smart-contracts/contract/src/handlers.rs rename to smart_contracts/contract/src/handlers.rs diff --git a/smart-contracts/contract/src/lib.rs b/smart_contracts/contract/src/lib.rs similarity index 100% rename from smart-contracts/contract/src/lib.rs rename to smart_contracts/contract/src/lib.rs diff --git a/smart-contracts/contract/src/unwrap_or_revert.rs b/smart_contracts/contract/src/unwrap_or_revert.rs similarity index 100% rename from smart-contracts/contract/src/unwrap_or_revert.rs rename to smart_contracts/contract/src/unwrap_or_revert.rs diff --git a/smart-contracts/contract/tests/version_numbers.rs b/smart_contracts/contract/tests/version_numbers.rs similarity index 100% rename from smart-contracts/contract/tests/version_numbers.rs rename to smart_contracts/contract/tests/version_numbers.rs diff --git a/smart-contracts/contract-as/.gitignore b/smart_contracts/contract_as/.gitignore similarity index 100% rename from smart-contracts/contract-as/.gitignore rename to smart_contracts/contract_as/.gitignore diff --git a/smart-contracts/contract-as/.npmignore b/smart_contracts/contract_as/.npmignore similarity index 100% rename from smart-contracts/contract-as/.npmignore rename to smart_contracts/contract_as/.npmignore diff --git a/smart-contracts/contract-as/.npmrc b/smart_contracts/contract_as/.npmrc similarity index 100% rename from smart-contracts/contract-as/.npmrc rename to smart_contracts/contract_as/.npmrc diff --git a/smart-contracts/contract-as/README.md b/smart_contracts/contract_as/README.md similarity index 97% rename from smart-contracts/contract-as/README.md rename to smart_contracts/contract_as/README.md index 7a2a2232e4..a15b3b9572 100644 --- a/smart-contracts/contract-as/README.md +++ b/smart_contracts/contract_as/README.md @@ -80,7 +80,7 @@ export function call(): void { Error.fromErrorCode(ErrorCode.None).revert(); // ErrorCode: 1 } ``` -If you prefer a more complicated first contract, you can look at client contracts on the [CasperLabs](https://github.com/CasperLabs/CasperLabs/tree/master/execution-engine/contracts-as/client) github repository for inspiration. +If you prefer a more complicated first contract, you can look at client contracts on the [CasperLabs](https://github.com/CasperLabs/CasperLabs/tree/master/execution-engine/contracts_as/client) github repository for inspiration. ### Compile to wasm To compile your contract to wasm, use npm to run the asbuild script from your project root. diff --git a/smart-contracts/contract-as/assembly/account.ts b/smart_contracts/contract_as/assembly/account.ts similarity index 100% rename from smart-contracts/contract-as/assembly/account.ts rename to smart_contracts/contract_as/assembly/account.ts diff --git a/smart-contracts/contract-as/assembly/bignum.ts b/smart_contracts/contract_as/assembly/bignum.ts similarity index 100% rename from smart-contracts/contract-as/assembly/bignum.ts rename to smart_contracts/contract_as/assembly/bignum.ts diff --git a/smart-contracts/contract-as/assembly/bytesrepr.ts b/smart_contracts/contract_as/assembly/bytesrepr.ts similarity index 100% rename from smart-contracts/contract-as/assembly/bytesrepr.ts rename to smart_contracts/contract_as/assembly/bytesrepr.ts diff --git a/smart-contracts/contract-as/assembly/clvalue.ts b/smart_contracts/contract_as/assembly/clvalue.ts similarity index 100% rename from smart-contracts/contract-as/assembly/clvalue.ts rename to smart_contracts/contract_as/assembly/clvalue.ts diff --git a/smart-contracts/contract-as/assembly/constants.ts b/smart_contracts/contract_as/assembly/constants.ts similarity index 100% rename from smart-contracts/contract-as/assembly/constants.ts rename to smart_contracts/contract_as/assembly/constants.ts diff --git a/smart-contracts/contract-as/assembly/contracts.ts b/smart_contracts/contract_as/assembly/contracts.ts similarity index 100% rename from smart-contracts/contract-as/assembly/contracts.ts rename to smart_contracts/contract_as/assembly/contracts.ts diff --git a/smart-contracts/contract-as/assembly/error.ts b/smart_contracts/contract_as/assembly/error.ts similarity index 100% rename from smart-contracts/contract-as/assembly/error.ts rename to smart_contracts/contract_as/assembly/error.ts diff --git a/smart-contracts/contract-as/assembly/externals.ts b/smart_contracts/contract_as/assembly/externals.ts similarity index 100% rename from smart-contracts/contract-as/assembly/externals.ts rename to smart_contracts/contract_as/assembly/externals.ts diff --git a/smart-contracts/contract-as/assembly/index.ts b/smart_contracts/contract_as/assembly/index.ts similarity index 100% rename from smart-contracts/contract-as/assembly/index.ts rename to smart_contracts/contract_as/assembly/index.ts diff --git a/smart-contracts/contract-as/assembly/key.ts b/smart_contracts/contract_as/assembly/key.ts similarity index 100% rename from smart-contracts/contract-as/assembly/key.ts rename to smart_contracts/contract_as/assembly/key.ts diff --git a/smart-contracts/contract-as/assembly/local.ts b/smart_contracts/contract_as/assembly/local.ts similarity index 100% rename from smart-contracts/contract-as/assembly/local.ts rename to smart_contracts/contract_as/assembly/local.ts diff --git a/smart-contracts/contract-as/assembly/option.ts b/smart_contracts/contract_as/assembly/option.ts similarity index 100% rename from smart-contracts/contract-as/assembly/option.ts rename to smart_contracts/contract_as/assembly/option.ts diff --git a/smart-contracts/contract-as/assembly/pair.ts b/smart_contracts/contract_as/assembly/pair.ts similarity index 100% rename from smart-contracts/contract-as/assembly/pair.ts rename to smart_contracts/contract_as/assembly/pair.ts diff --git a/smart-contracts/contract-as/assembly/purse.ts b/smart_contracts/contract_as/assembly/purse.ts similarity index 100% rename from smart-contracts/contract-as/assembly/purse.ts rename to smart_contracts/contract_as/assembly/purse.ts diff --git a/smart-contracts/contract-as/assembly/runtime_args.ts b/smart_contracts/contract_as/assembly/runtime_args.ts similarity index 100% rename from smart-contracts/contract-as/assembly/runtime_args.ts rename to smart_contracts/contract_as/assembly/runtime_args.ts diff --git a/smart-contracts/contract-as/assembly/tsconfig.json b/smart_contracts/contract_as/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contract-as/assembly/tsconfig.json rename to smart_contracts/contract_as/assembly/tsconfig.json diff --git a/smart-contracts/contract-as/assembly/unit.ts b/smart_contracts/contract_as/assembly/unit.ts similarity index 100% rename from smart-contracts/contract-as/assembly/unit.ts rename to smart_contracts/contract_as/assembly/unit.ts diff --git a/smart-contracts/contract-as/assembly/uref.ts b/smart_contracts/contract_as/assembly/uref.ts similarity index 100% rename from smart-contracts/contract-as/assembly/uref.ts rename to smart_contracts/contract_as/assembly/uref.ts diff --git a/smart-contracts/contract-as/assembly/utils.ts b/smart_contracts/contract_as/assembly/utils.ts similarity index 100% rename from smart-contracts/contract-as/assembly/utils.ts rename to smart_contracts/contract_as/assembly/utils.ts diff --git a/smart-contracts/contract-as/build/.gitignore b/smart_contracts/contract_as/build/.gitignore similarity index 100% rename from smart-contracts/contract-as/build/.gitignore rename to smart_contracts/contract_as/build/.gitignore diff --git a/smart-contracts/contract-as/index.js b/smart_contracts/contract_as/index.js similarity index 100% rename from smart-contracts/contract-as/index.js rename to smart_contracts/contract_as/index.js diff --git a/smart-contracts/contract-as/package-lock.json b/smart_contracts/contract_as/package-lock.json similarity index 100% rename from smart-contracts/contract-as/package-lock.json rename to smart_contracts/contract_as/package-lock.json diff --git a/smart-contracts/contract-as/package.json b/smart_contracts/contract_as/package.json similarity index 100% rename from smart-contracts/contract-as/package.json rename to smart_contracts/contract_as/package.json diff --git a/smart-contracts/contract-as/tests/assembly/bignum.spec.as.ts b/smart_contracts/contract_as/tests/assembly/bignum.spec.as.ts similarity index 100% rename from smart-contracts/contract-as/tests/assembly/bignum.spec.as.ts rename to smart_contracts/contract_as/tests/assembly/bignum.spec.as.ts diff --git a/smart-contracts/contract-as/tests/assembly/bytesrepr.spec.as.ts b/smart_contracts/contract_as/tests/assembly/bytesrepr.spec.as.ts similarity index 100% rename from smart-contracts/contract-as/tests/assembly/bytesrepr.spec.as.ts rename to smart_contracts/contract_as/tests/assembly/bytesrepr.spec.as.ts diff --git a/smart-contracts/contract-as/tests/assembly/runtime_args.spec.as.ts b/smart_contracts/contract_as/tests/assembly/runtime_args.spec.as.ts similarity index 100% rename from smart-contracts/contract-as/tests/assembly/runtime_args.spec.as.ts rename to smart_contracts/contract_as/tests/assembly/runtime_args.spec.as.ts diff --git a/smart-contracts/contract-as/tests/assembly/utils.spec.as.ts b/smart_contracts/contract_as/tests/assembly/utils.spec.as.ts similarity index 100% rename from smart-contracts/contract-as/tests/assembly/utils.spec.as.ts rename to smart_contracts/contract_as/tests/assembly/utils.spec.as.ts diff --git a/smart-contracts/contract-as/tests/bignum.spec.ts b/smart_contracts/contract_as/tests/bignum.spec.ts similarity index 100% rename from smart-contracts/contract-as/tests/bignum.spec.ts rename to smart_contracts/contract_as/tests/bignum.spec.ts diff --git a/smart-contracts/contract-as/tests/bytesrepr.spec.ts b/smart_contracts/contract_as/tests/bytesrepr.spec.ts similarity index 100% rename from smart-contracts/contract-as/tests/bytesrepr.spec.ts rename to smart_contracts/contract_as/tests/bytesrepr.spec.ts diff --git a/smart-contracts/contract-as/tests/runtime_args.spec.ts b/smart_contracts/contract_as/tests/runtime_args.spec.ts similarity index 100% rename from smart-contracts/contract-as/tests/runtime_args.spec.ts rename to smart_contracts/contract_as/tests/runtime_args.spec.ts diff --git a/smart-contracts/contract-as/tests/tsconfig.json b/smart_contracts/contract_as/tests/tsconfig.json similarity index 100% rename from smart-contracts/contract-as/tests/tsconfig.json rename to smart_contracts/contract_as/tests/tsconfig.json diff --git a/smart-contracts/contract-as/tests/utils.spec.ts b/smart_contracts/contract_as/tests/utils.spec.ts similarity index 100% rename from smart-contracts/contract-as/tests/utils.spec.ts rename to smart_contracts/contract_as/tests/utils.spec.ts diff --git a/smart-contracts/contract-as/tests/utils/helpers.ts b/smart_contracts/contract_as/tests/utils/helpers.ts similarity index 100% rename from smart-contracts/contract-as/tests/utils/helpers.ts rename to smart_contracts/contract_as/tests/utils/helpers.ts diff --git a/smart-contracts/contract-as/tests/utils/spec.ts b/smart_contracts/contract_as/tests/utils/spec.ts similarity index 100% rename from smart-contracts/contract-as/tests/utils/spec.ts rename to smart_contracts/contract_as/tests/utils/spec.ts diff --git a/smart-contracts/contracts/.cargo/config b/smart_contracts/contracts/.cargo/config similarity index 100% rename from smart-contracts/contracts/.cargo/config rename to smart_contracts/contracts/.cargo/config diff --git a/smart-contracts/contracts/SRE/create-test-node-01/Cargo.toml b/smart_contracts/contracts/SRE/create-test-node-01/Cargo.toml similarity index 100% rename from smart-contracts/contracts/SRE/create-test-node-01/Cargo.toml rename to smart_contracts/contracts/SRE/create-test-node-01/Cargo.toml diff --git a/smart-contracts/contracts/SRE/create-test-node-01/src/main.rs b/smart_contracts/contracts/SRE/create-test-node-01/src/main.rs similarity index 100% rename from smart-contracts/contracts/SRE/create-test-node-01/src/main.rs rename to smart_contracts/contracts/SRE/create-test-node-01/src/main.rs diff --git a/smart-contracts/contracts/SRE/create-test-node-02/Cargo.toml b/smart_contracts/contracts/SRE/create-test-node-02/Cargo.toml similarity index 100% rename from smart-contracts/contracts/SRE/create-test-node-02/Cargo.toml rename to smart_contracts/contracts/SRE/create-test-node-02/Cargo.toml diff --git a/smart-contracts/contracts/SRE/create-test-node-02/src/main.rs b/smart_contracts/contracts/SRE/create-test-node-02/src/main.rs similarity index 100% rename from smart-contracts/contracts/SRE/create-test-node-02/src/main.rs rename to smart_contracts/contracts/SRE/create-test-node-02/src/main.rs diff --git a/smart-contracts/contracts/SRE/create-test-node-03/Cargo.toml b/smart_contracts/contracts/SRE/create-test-node-03/Cargo.toml similarity index 100% rename from smart-contracts/contracts/SRE/create-test-node-03/Cargo.toml rename to smart_contracts/contracts/SRE/create-test-node-03/Cargo.toml diff --git a/smart-contracts/contracts/SRE/create-test-node-03/src/main.rs b/smart_contracts/contracts/SRE/create-test-node-03/src/main.rs similarity index 100% rename from smart-contracts/contracts/SRE/create-test-node-03/src/main.rs rename to smart_contracts/contracts/SRE/create-test-node-03/src/main.rs diff --git a/smart-contracts/contracts/SRE/create-test-node-shared/Cargo.toml b/smart_contracts/contracts/SRE/create-test-node-shared/Cargo.toml similarity index 100% rename from smart-contracts/contracts/SRE/create-test-node-shared/Cargo.toml rename to smart_contracts/contracts/SRE/create-test-node-shared/Cargo.toml diff --git a/smart-contracts/contracts/SRE/create-test-node-shared/src/lib.rs b/smart_contracts/contracts/SRE/create-test-node-shared/src/lib.rs similarity index 100% rename from smart-contracts/contracts/SRE/create-test-node-shared/src/lib.rs rename to smart_contracts/contracts/SRE/create-test-node-shared/src/lib.rs diff --git a/smart-contracts/contracts/bench/create-accounts/Cargo.toml b/smart_contracts/contracts/bench/create-accounts/Cargo.toml similarity index 100% rename from smart-contracts/contracts/bench/create-accounts/Cargo.toml rename to smart_contracts/contracts/bench/create-accounts/Cargo.toml diff --git a/smart-contracts/contracts/bench/create-accounts/src/main.rs b/smart_contracts/contracts/bench/create-accounts/src/main.rs similarity index 100% rename from smart-contracts/contracts/bench/create-accounts/src/main.rs rename to smart_contracts/contracts/bench/create-accounts/src/main.rs diff --git a/smart-contracts/contracts/bench/create-purses/Cargo.toml b/smart_contracts/contracts/bench/create-purses/Cargo.toml similarity index 100% rename from smart-contracts/contracts/bench/create-purses/Cargo.toml rename to smart_contracts/contracts/bench/create-purses/Cargo.toml diff --git a/smart-contracts/contracts/bench/create-purses/src/main.rs b/smart_contracts/contracts/bench/create-purses/src/main.rs similarity index 100% rename from smart-contracts/contracts/bench/create-purses/src/main.rs rename to smart_contracts/contracts/bench/create-purses/src/main.rs diff --git a/smart-contracts/contracts/bench/transfer-to-existing-account/Cargo.toml b/smart_contracts/contracts/bench/transfer-to-existing-account/Cargo.toml similarity index 100% rename from smart-contracts/contracts/bench/transfer-to-existing-account/Cargo.toml rename to smart_contracts/contracts/bench/transfer-to-existing-account/Cargo.toml diff --git a/smart-contracts/contracts/bench/transfer-to-existing-account/src/main.rs b/smart_contracts/contracts/bench/transfer-to-existing-account/src/main.rs similarity index 100% rename from smart-contracts/contracts/bench/transfer-to-existing-account/src/main.rs rename to smart_contracts/contracts/bench/transfer-to-existing-account/src/main.rs diff --git a/smart-contracts/contracts/bench/transfer-to-purse/Cargo.toml b/smart_contracts/contracts/bench/transfer-to-purse/Cargo.toml similarity index 100% rename from smart-contracts/contracts/bench/transfer-to-purse/Cargo.toml rename to smart_contracts/contracts/bench/transfer-to-purse/Cargo.toml diff --git a/smart-contracts/contracts/bench/transfer-to-purse/src/main.rs b/smart_contracts/contracts/bench/transfer-to-purse/src/main.rs similarity index 100% rename from smart-contracts/contracts/bench/transfer-to-purse/src/main.rs rename to smart_contracts/contracts/bench/transfer-to-purse/src/main.rs diff --git a/smart-contracts/contracts/client/bonding/Cargo.toml b/smart_contracts/contracts/client/bonding/Cargo.toml similarity index 100% rename from smart-contracts/contracts/client/bonding/Cargo.toml rename to smart_contracts/contracts/client/bonding/Cargo.toml diff --git a/smart-contracts/contracts/client/bonding/src/main.rs b/smart_contracts/contracts/client/bonding/src/main.rs similarity index 100% rename from smart-contracts/contracts/client/bonding/src/main.rs rename to smart_contracts/contracts/client/bonding/src/main.rs diff --git a/smart-contracts/contracts/client/counter-define/Cargo.toml b/smart_contracts/contracts/client/counter-define/Cargo.toml similarity index 100% rename from smart-contracts/contracts/client/counter-define/Cargo.toml rename to smart_contracts/contracts/client/counter-define/Cargo.toml diff --git a/smart-contracts/contracts/client/counter-define/src/main.rs b/smart_contracts/contracts/client/counter-define/src/main.rs similarity index 100% rename from smart-contracts/contracts/client/counter-define/src/main.rs rename to smart_contracts/contracts/client/counter-define/src/main.rs diff --git a/smart-contracts/contracts/client/named-purse-payment/Cargo.toml b/smart_contracts/contracts/client/named-purse-payment/Cargo.toml similarity index 100% rename from smart-contracts/contracts/client/named-purse-payment/Cargo.toml rename to smart_contracts/contracts/client/named-purse-payment/Cargo.toml diff --git a/smart-contracts/contracts/client/named-purse-payment/src/main.rs b/smart_contracts/contracts/client/named-purse-payment/src/main.rs similarity index 100% rename from smart-contracts/contracts/client/named-purse-payment/src/main.rs rename to smart_contracts/contracts/client/named-purse-payment/src/main.rs diff --git a/smart-contracts/contracts/client/revert/Cargo.toml b/smart_contracts/contracts/client/revert/Cargo.toml similarity index 100% rename from smart-contracts/contracts/client/revert/Cargo.toml rename to smart_contracts/contracts/client/revert/Cargo.toml diff --git a/smart-contracts/contracts/client/revert/src/main.rs b/smart_contracts/contracts/client/revert/src/main.rs similarity index 100% rename from smart-contracts/contracts/client/revert/src/main.rs rename to smart_contracts/contracts/client/revert/src/main.rs diff --git a/smart-contracts/contracts/client/transfer-to-account-stored/Cargo.toml b/smart_contracts/contracts/client/transfer-to-account-stored/Cargo.toml similarity index 100% rename from smart-contracts/contracts/client/transfer-to-account-stored/Cargo.toml rename to smart_contracts/contracts/client/transfer-to-account-stored/Cargo.toml diff --git a/smart-contracts/contracts/client/transfer-to-account-stored/src/main.rs b/smart_contracts/contracts/client/transfer-to-account-stored/src/main.rs similarity index 100% rename from smart-contracts/contracts/client/transfer-to-account-stored/src/main.rs rename to smart_contracts/contracts/client/transfer-to-account-stored/src/main.rs diff --git a/smart-contracts/contracts/client/transfer-to-account-u512-stored/Cargo.toml b/smart_contracts/contracts/client/transfer-to-account-u512-stored/Cargo.toml similarity index 100% rename from smart-contracts/contracts/client/transfer-to-account-u512-stored/Cargo.toml rename to smart_contracts/contracts/client/transfer-to-account-u512-stored/Cargo.toml diff --git a/smart-contracts/contracts/client/transfer-to-account-u512-stored/src/main.rs b/smart_contracts/contracts/client/transfer-to-account-u512-stored/src/main.rs similarity index 100% rename from smart-contracts/contracts/client/transfer-to-account-u512-stored/src/main.rs rename to smart_contracts/contracts/client/transfer-to-account-u512-stored/src/main.rs diff --git a/smart-contracts/contracts/client/transfer-to-account-u512/Cargo.toml b/smart_contracts/contracts/client/transfer-to-account-u512/Cargo.toml similarity index 100% rename from smart-contracts/contracts/client/transfer-to-account-u512/Cargo.toml rename to smart_contracts/contracts/client/transfer-to-account-u512/Cargo.toml diff --git a/smart-contracts/contracts/client/transfer-to-account-u512/src/bin/main.rs b/smart_contracts/contracts/client/transfer-to-account-u512/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/client/transfer-to-account-u512/src/bin/main.rs rename to smart_contracts/contracts/client/transfer-to-account-u512/src/bin/main.rs diff --git a/smart-contracts/contracts/client/transfer-to-account-u512/src/lib.rs b/smart_contracts/contracts/client/transfer-to-account-u512/src/lib.rs similarity index 100% rename from smart-contracts/contracts/client/transfer-to-account-u512/src/lib.rs rename to smart_contracts/contracts/client/transfer-to-account-u512/src/lib.rs diff --git a/smart-contracts/contracts/client/transfer-to-account/Cargo.toml b/smart_contracts/contracts/client/transfer-to-account/Cargo.toml similarity index 100% rename from smart-contracts/contracts/client/transfer-to-account/Cargo.toml rename to smart_contracts/contracts/client/transfer-to-account/Cargo.toml diff --git a/smart-contracts/contracts/client/transfer-to-account/src/bin/main.rs b/smart_contracts/contracts/client/transfer-to-account/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/client/transfer-to-account/src/bin/main.rs rename to smart_contracts/contracts/client/transfer-to-account/src/bin/main.rs diff --git a/smart-contracts/contracts/client/transfer-to-account/src/lib.rs b/smart_contracts/contracts/client/transfer-to-account/src/lib.rs similarity index 100% rename from smart-contracts/contracts/client/transfer-to-account/src/lib.rs rename to smart_contracts/contracts/client/transfer-to-account/src/lib.rs diff --git a/smart-contracts/contracts/client/unbonding/Cargo.toml b/smart_contracts/contracts/client/unbonding/Cargo.toml similarity index 100% rename from smart-contracts/contracts/client/unbonding/Cargo.toml rename to smart_contracts/contracts/client/unbonding/Cargo.toml diff --git a/smart-contracts/contracts/client/unbonding/src/main.rs b/smart_contracts/contracts/client/unbonding/src/main.rs similarity index 100% rename from smart-contracts/contracts/client/unbonding/src/main.rs rename to smart_contracts/contracts/client/unbonding/src/main.rs diff --git a/smart-contracts/contracts/explorer/faucet-stored/Cargo.toml b/smart_contracts/contracts/explorer/faucet-stored/Cargo.toml similarity index 100% rename from smart-contracts/contracts/explorer/faucet-stored/Cargo.toml rename to smart_contracts/contracts/explorer/faucet-stored/Cargo.toml diff --git a/smart-contracts/contracts/explorer/faucet-stored/src/main.rs b/smart_contracts/contracts/explorer/faucet-stored/src/main.rs similarity index 100% rename from smart-contracts/contracts/explorer/faucet-stored/src/main.rs rename to smart_contracts/contracts/explorer/faucet-stored/src/main.rs diff --git a/smart-contracts/contracts/explorer/faucet/Cargo.toml b/smart_contracts/contracts/explorer/faucet/Cargo.toml similarity index 100% rename from smart-contracts/contracts/explorer/faucet/Cargo.toml rename to smart_contracts/contracts/explorer/faucet/Cargo.toml diff --git a/smart-contracts/contracts/explorer/faucet/src/bin/main.rs b/smart_contracts/contracts/explorer/faucet/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/explorer/faucet/src/bin/main.rs rename to smart_contracts/contracts/explorer/faucet/src/bin/main.rs diff --git a/smart-contracts/contracts/explorer/faucet/src/lib.rs b/smart_contracts/contracts/explorer/faucet/src/lib.rs similarity index 100% rename from smart-contracts/contracts/explorer/faucet/src/lib.rs rename to smart_contracts/contracts/explorer/faucet/src/lib.rs diff --git a/smart-contracts/contracts/integration/add-associated-key/Cargo.toml b/smart_contracts/contracts/integration/add-associated-key/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/add-associated-key/Cargo.toml rename to smart_contracts/contracts/integration/add-associated-key/Cargo.toml diff --git a/smart-contracts/contracts/integration/add-associated-key/src/main.rs b/smart_contracts/contracts/integration/add-associated-key/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/add-associated-key/src/main.rs rename to smart_contracts/contracts/integration/add-associated-key/src/main.rs diff --git a/smart-contracts/contracts/integration/args-multi/Cargo.toml b/smart_contracts/contracts/integration/args-multi/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/args-multi/Cargo.toml rename to smart_contracts/contracts/integration/args-multi/Cargo.toml diff --git a/smart-contracts/contracts/integration/args-multi/src/main.rs b/smart_contracts/contracts/integration/args-multi/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/args-multi/src/main.rs rename to smart_contracts/contracts/integration/args-multi/src/main.rs diff --git a/smart-contracts/contracts/integration/args-u32/Cargo.toml b/smart_contracts/contracts/integration/args-u32/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/args-u32/Cargo.toml rename to smart_contracts/contracts/integration/args-u32/Cargo.toml diff --git a/smart-contracts/contracts/integration/args-u32/src/main.rs b/smart_contracts/contracts/integration/args-u32/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/args-u32/src/main.rs rename to smart_contracts/contracts/integration/args-u32/src/main.rs diff --git a/smart-contracts/contracts/integration/args-u512/Cargo.toml b/smart_contracts/contracts/integration/args-u512/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/args-u512/Cargo.toml rename to smart_contracts/contracts/integration/args-u512/Cargo.toml diff --git a/smart-contracts/contracts/integration/args-u512/src/main.rs b/smart_contracts/contracts/integration/args-u512/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/args-u512/src/main.rs rename to smart_contracts/contracts/integration/args-u512/src/main.rs diff --git a/smart-contracts/contracts/integration/create-named-purse/Cargo.toml b/smart_contracts/contracts/integration/create-named-purse/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/create-named-purse/Cargo.toml rename to smart_contracts/contracts/integration/create-named-purse/Cargo.toml diff --git a/smart-contracts/contracts/integration/create-named-purse/src/main.rs b/smart_contracts/contracts/integration/create-named-purse/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/create-named-purse/src/main.rs rename to smart_contracts/contracts/integration/create-named-purse/src/main.rs diff --git a/smart-contracts/contracts/integration/direct-revert/Cargo.toml b/smart_contracts/contracts/integration/direct-revert/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/direct-revert/Cargo.toml rename to smart_contracts/contracts/integration/direct-revert/Cargo.toml diff --git a/smart-contracts/contracts/integration/direct-revert/src/main.rs b/smart_contracts/contracts/integration/direct-revert/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/direct-revert/src/main.rs rename to smart_contracts/contracts/integration/direct-revert/src/main.rs diff --git a/smart-contracts/contracts/integration/get-caller-call/Cargo.toml b/smart_contracts/contracts/integration/get-caller-call/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/get-caller-call/Cargo.toml rename to smart_contracts/contracts/integration/get-caller-call/Cargo.toml diff --git a/smart-contracts/contracts/integration/get-caller-call/src/main.rs b/smart_contracts/contracts/integration/get-caller-call/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/get-caller-call/src/main.rs rename to smart_contracts/contracts/integration/get-caller-call/src/main.rs diff --git a/smart-contracts/contracts/integration/get-caller-define/Cargo.toml b/smart_contracts/contracts/integration/get-caller-define/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/get-caller-define/Cargo.toml rename to smart_contracts/contracts/integration/get-caller-define/Cargo.toml diff --git a/smart-contracts/contracts/integration/get-caller-define/src/main.rs b/smart_contracts/contracts/integration/get-caller-define/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/get-caller-define/src/main.rs rename to smart_contracts/contracts/integration/get-caller-define/src/main.rs diff --git a/smart-contracts/contracts/integration/list-known-urefs-call/Cargo.toml b/smart_contracts/contracts/integration/list-known-urefs-call/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/list-known-urefs-call/Cargo.toml rename to smart_contracts/contracts/integration/list-known-urefs-call/Cargo.toml diff --git a/smart-contracts/contracts/integration/list-known-urefs-call/src/main.rs b/smart_contracts/contracts/integration/list-known-urefs-call/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/list-known-urefs-call/src/main.rs rename to smart_contracts/contracts/integration/list-known-urefs-call/src/main.rs diff --git a/smart-contracts/contracts/integration/list-known-urefs-define/Cargo.toml b/smart_contracts/contracts/integration/list-known-urefs-define/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/list-known-urefs-define/Cargo.toml rename to smart_contracts/contracts/integration/list-known-urefs-define/Cargo.toml diff --git a/smart-contracts/contracts/integration/list-known-urefs-define/src/main.rs b/smart_contracts/contracts/integration/list-known-urefs-define/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/list-known-urefs-define/src/main.rs rename to smart_contracts/contracts/integration/list-known-urefs-define/src/main.rs diff --git a/smart-contracts/contracts/integration/payment-from-named-purse/Cargo.toml b/smart_contracts/contracts/integration/payment-from-named-purse/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/payment-from-named-purse/Cargo.toml rename to smart_contracts/contracts/integration/payment-from-named-purse/Cargo.toml diff --git a/smart-contracts/contracts/integration/payment-from-named-purse/src/main.rs b/smart_contracts/contracts/integration/payment-from-named-purse/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/payment-from-named-purse/src/main.rs rename to smart_contracts/contracts/integration/payment-from-named-purse/src/main.rs diff --git a/smart-contracts/contracts/integration/set-key-thresholds/Cargo.toml b/smart_contracts/contracts/integration/set-key-thresholds/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/set-key-thresholds/Cargo.toml rename to smart_contracts/contracts/integration/set-key-thresholds/Cargo.toml diff --git a/smart-contracts/contracts/integration/set-key-thresholds/src/main.rs b/smart_contracts/contracts/integration/set-key-thresholds/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/set-key-thresholds/src/main.rs rename to smart_contracts/contracts/integration/set-key-thresholds/src/main.rs diff --git a/smart-contracts/contracts/integration/subcall-revert-call/Cargo.toml b/smart_contracts/contracts/integration/subcall-revert-call/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/subcall-revert-call/Cargo.toml rename to smart_contracts/contracts/integration/subcall-revert-call/Cargo.toml diff --git a/smart-contracts/contracts/integration/subcall-revert-call/src/main.rs b/smart_contracts/contracts/integration/subcall-revert-call/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/subcall-revert-call/src/main.rs rename to smart_contracts/contracts/integration/subcall-revert-call/src/main.rs diff --git a/smart-contracts/contracts/integration/subcall-revert-define/Cargo.toml b/smart_contracts/contracts/integration/subcall-revert-define/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/subcall-revert-define/Cargo.toml rename to smart_contracts/contracts/integration/subcall-revert-define/Cargo.toml diff --git a/smart-contracts/contracts/integration/subcall-revert-define/src/main.rs b/smart_contracts/contracts/integration/subcall-revert-define/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/subcall-revert-define/src/main.rs rename to smart_contracts/contracts/integration/subcall-revert-define/src/main.rs diff --git a/smart-contracts/contracts/integration/update-associated-key/Cargo.toml b/smart_contracts/contracts/integration/update-associated-key/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/update-associated-key/Cargo.toml rename to smart_contracts/contracts/integration/update-associated-key/Cargo.toml diff --git a/smart-contracts/contracts/integration/update-associated-key/src/main.rs b/smart_contracts/contracts/integration/update-associated-key/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/update-associated-key/src/main.rs rename to smart_contracts/contracts/integration/update-associated-key/src/main.rs diff --git a/smart-contracts/contracts/integration/write-all-types/Cargo.toml b/smart_contracts/contracts/integration/write-all-types/Cargo.toml similarity index 100% rename from smart-contracts/contracts/integration/write-all-types/Cargo.toml rename to smart_contracts/contracts/integration/write-all-types/Cargo.toml diff --git a/smart-contracts/contracts/integration/write-all-types/src/main.rs b/smart_contracts/contracts/integration/write-all-types/src/main.rs similarity index 100% rename from smart-contracts/contracts/integration/write-all-types/src/main.rs rename to smart_contracts/contracts/integration/write-all-types/src/main.rs diff --git a/smart-contracts/contracts/profiling/host-function-metrics/Cargo.toml b/smart_contracts/contracts/profiling/host-function-metrics/Cargo.toml similarity index 100% rename from smart-contracts/contracts/profiling/host-function-metrics/Cargo.toml rename to smart_contracts/contracts/profiling/host-function-metrics/Cargo.toml diff --git a/smart-contracts/contracts/profiling/host-function-metrics/src/lib.rs b/smart_contracts/contracts/profiling/host-function-metrics/src/lib.rs similarity index 100% rename from smart-contracts/contracts/profiling/host-function-metrics/src/lib.rs rename to smart_contracts/contracts/profiling/host-function-metrics/src/lib.rs diff --git a/smart-contracts/contracts/profiling/simple-transfer/Cargo.toml b/smart_contracts/contracts/profiling/simple-transfer/Cargo.toml similarity index 100% rename from smart-contracts/contracts/profiling/simple-transfer/Cargo.toml rename to smart_contracts/contracts/profiling/simple-transfer/Cargo.toml diff --git a/smart-contracts/contracts/profiling/simple-transfer/src/main.rs b/smart_contracts/contracts/profiling/simple-transfer/src/main.rs similarity index 100% rename from smart-contracts/contracts/profiling/simple-transfer/src/main.rs rename to smart_contracts/contracts/profiling/simple-transfer/src/main.rs diff --git a/smart-contracts/contracts/profiling/state-initializer/Cargo.toml b/smart_contracts/contracts/profiling/state-initializer/Cargo.toml similarity index 100% rename from smart-contracts/contracts/profiling/state-initializer/Cargo.toml rename to smart_contracts/contracts/profiling/state-initializer/Cargo.toml diff --git a/smart-contracts/contracts/profiling/state-initializer/src/main.rs b/smart_contracts/contracts/profiling/state-initializer/src/main.rs similarity index 100% rename from smart-contracts/contracts/profiling/state-initializer/src/main.rs rename to smart_contracts/contracts/profiling/state-initializer/src/main.rs diff --git a/smart-contracts/contracts/system/mint-install/Cargo.toml b/smart_contracts/contracts/system/mint-install/Cargo.toml similarity index 100% rename from smart-contracts/contracts/system/mint-install/Cargo.toml rename to smart_contracts/contracts/system/mint-install/Cargo.toml diff --git a/smart-contracts/contracts/system/mint-install/src/main.rs b/smart_contracts/contracts/system/mint-install/src/main.rs similarity index 100% rename from smart-contracts/contracts/system/mint-install/src/main.rs rename to smart_contracts/contracts/system/mint-install/src/main.rs diff --git a/smart-contracts/contracts/system/mint-token/Cargo.toml b/smart_contracts/contracts/system/mint-token/Cargo.toml similarity index 100% rename from smart-contracts/contracts/system/mint-token/Cargo.toml rename to smart_contracts/contracts/system/mint-token/Cargo.toml diff --git a/smart-contracts/contracts/system/mint-token/src/bin/main.rs b/smart_contracts/contracts/system/mint-token/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/system/mint-token/src/bin/main.rs rename to smart_contracts/contracts/system/mint-token/src/bin/main.rs diff --git a/smart-contracts/contracts/system/mint-token/src/lib.rs b/smart_contracts/contracts/system/mint-token/src/lib.rs similarity index 100% rename from smart-contracts/contracts/system/mint-token/src/lib.rs rename to smart_contracts/contracts/system/mint-token/src/lib.rs diff --git a/smart-contracts/contracts/system/pos-install/Cargo.toml b/smart_contracts/contracts/system/pos-install/Cargo.toml similarity index 100% rename from smart-contracts/contracts/system/pos-install/Cargo.toml rename to smart_contracts/contracts/system/pos-install/Cargo.toml diff --git a/smart-contracts/contracts/system/pos-install/src/main.rs b/smart_contracts/contracts/system/pos-install/src/main.rs similarity index 100% rename from smart-contracts/contracts/system/pos-install/src/main.rs rename to smart_contracts/contracts/system/pos-install/src/main.rs diff --git a/smart-contracts/contracts/system/pos/Cargo.toml b/smart_contracts/contracts/system/pos/Cargo.toml similarity index 100% rename from smart-contracts/contracts/system/pos/Cargo.toml rename to smart_contracts/contracts/system/pos/Cargo.toml diff --git a/smart-contracts/contracts/system/pos/src/bin/main.rs b/smart_contracts/contracts/system/pos/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/system/pos/src/bin/main.rs rename to smart_contracts/contracts/system/pos/src/bin/main.rs diff --git a/smart-contracts/contracts/system/pos/src/lib.rs b/smart_contracts/contracts/system/pos/src/lib.rs similarity index 100% rename from smart-contracts/contracts/system/pos/src/lib.rs rename to smart_contracts/contracts/system/pos/src/lib.rs diff --git a/smart-contracts/contracts/system/standard-payment-install/Cargo.toml b/smart_contracts/contracts/system/standard-payment-install/Cargo.toml similarity index 100% rename from smart-contracts/contracts/system/standard-payment-install/Cargo.toml rename to smart_contracts/contracts/system/standard-payment-install/Cargo.toml diff --git a/smart-contracts/contracts/system/standard-payment-install/src/main.rs b/smart_contracts/contracts/system/standard-payment-install/src/main.rs similarity index 100% rename from smart-contracts/contracts/system/standard-payment-install/src/main.rs rename to smart_contracts/contracts/system/standard-payment-install/src/main.rs diff --git a/smart-contracts/contracts/system/standard-payment/Cargo.toml b/smart_contracts/contracts/system/standard-payment/Cargo.toml similarity index 100% rename from smart-contracts/contracts/system/standard-payment/Cargo.toml rename to smart_contracts/contracts/system/standard-payment/Cargo.toml diff --git a/smart-contracts/contracts/system/standard-payment/src/bin/main.rs b/smart_contracts/contracts/system/standard-payment/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/system/standard-payment/src/bin/main.rs rename to smart_contracts/contracts/system/standard-payment/src/bin/main.rs diff --git a/smart-contracts/contracts/system/standard-payment/src/lib.rs b/smart_contracts/contracts/system/standard-payment/src/lib.rs similarity index 100% rename from smart-contracts/contracts/system/standard-payment/src/lib.rs rename to smart_contracts/contracts/system/standard-payment/src/lib.rs diff --git a/smart-contracts/contracts/system/test-mint-token/Cargo.toml b/smart_contracts/contracts/system/test-mint-token/Cargo.toml similarity index 100% rename from smart-contracts/contracts/system/test-mint-token/Cargo.toml rename to smart_contracts/contracts/system/test-mint-token/Cargo.toml diff --git a/smart-contracts/contracts/system/test-mint-token/src/main.rs b/smart_contracts/contracts/system/test-mint-token/src/main.rs similarity index 100% rename from smart-contracts/contracts/system/test-mint-token/src/main.rs rename to smart_contracts/contracts/system/test-mint-token/src/main.rs diff --git a/smart-contracts/contracts/test/add-gas-subcall/Cargo.toml b/smart_contracts/contracts/test/add-gas-subcall/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/add-gas-subcall/Cargo.toml rename to smart_contracts/contracts/test/add-gas-subcall/Cargo.toml diff --git a/smart-contracts/contracts/test/add-gas-subcall/src/main.rs b/smart_contracts/contracts/test/add-gas-subcall/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/add-gas-subcall/src/main.rs rename to smart_contracts/contracts/test/add-gas-subcall/src/main.rs diff --git a/smart-contracts/contracts/test/add-update-associated-key/Cargo.toml b/smart_contracts/contracts/test/add-update-associated-key/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/add-update-associated-key/Cargo.toml rename to smart_contracts/contracts/test/add-update-associated-key/Cargo.toml diff --git a/smart-contracts/contracts/test/add-update-associated-key/src/main.rs b/smart_contracts/contracts/test/add-update-associated-key/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/add-update-associated-key/src/main.rs rename to smart_contracts/contracts/test/add-update-associated-key/src/main.rs diff --git a/smart-contracts/contracts/test/authorized-keys/Cargo.toml b/smart_contracts/contracts/test/authorized-keys/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/authorized-keys/Cargo.toml rename to smart_contracts/contracts/test/authorized-keys/Cargo.toml diff --git a/smart-contracts/contracts/test/authorized-keys/src/main.rs b/smart_contracts/contracts/test/authorized-keys/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/authorized-keys/src/main.rs rename to smart_contracts/contracts/test/authorized-keys/src/main.rs diff --git a/smart-contracts/contracts/test/contract-context/Cargo.toml b/smart_contracts/contracts/test/contract-context/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/contract-context/Cargo.toml rename to smart_contracts/contracts/test/contract-context/Cargo.toml diff --git a/smart-contracts/contracts/test/contract-context/src/main.rs b/smart_contracts/contracts/test/contract-context/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/contract-context/src/main.rs rename to smart_contracts/contracts/test/contract-context/src/main.rs diff --git a/smart-contracts/contracts/test/create-purse-01/Cargo.toml b/smart_contracts/contracts/test/create-purse-01/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/create-purse-01/Cargo.toml rename to smart_contracts/contracts/test/create-purse-01/Cargo.toml diff --git a/smart-contracts/contracts/test/create-purse-01/src/bin/main.rs b/smart_contracts/contracts/test/create-purse-01/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/test/create-purse-01/src/bin/main.rs rename to smart_contracts/contracts/test/create-purse-01/src/bin/main.rs diff --git a/smart-contracts/contracts/test/create-purse-01/src/lib.rs b/smart_contracts/contracts/test/create-purse-01/src/lib.rs similarity index 100% rename from smart-contracts/contracts/test/create-purse-01/src/lib.rs rename to smart_contracts/contracts/test/create-purse-01/src/lib.rs diff --git a/smart-contracts/contracts/test/deserialize-error/Cargo.toml b/smart_contracts/contracts/test/deserialize-error/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/deserialize-error/Cargo.toml rename to smart_contracts/contracts/test/deserialize-error/Cargo.toml diff --git a/smart-contracts/contracts/test/deserialize-error/src/main.rs b/smart_contracts/contracts/test/deserialize-error/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/deserialize-error/src/main.rs rename to smart_contracts/contracts/test/deserialize-error/src/main.rs diff --git a/smart-contracts/contracts/test/do-nothing-stored-caller/Cargo.toml b/smart_contracts/contracts/test/do-nothing-stored-caller/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/do-nothing-stored-caller/Cargo.toml rename to smart_contracts/contracts/test/do-nothing-stored-caller/Cargo.toml diff --git a/smart-contracts/contracts/test/do-nothing-stored-caller/src/main.rs b/smart_contracts/contracts/test/do-nothing-stored-caller/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/do-nothing-stored-caller/src/main.rs rename to smart_contracts/contracts/test/do-nothing-stored-caller/src/main.rs diff --git a/smart-contracts/contracts/test/do-nothing-stored-upgrader/Cargo.toml b/smart_contracts/contracts/test/do-nothing-stored-upgrader/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/do-nothing-stored-upgrader/Cargo.toml rename to smart_contracts/contracts/test/do-nothing-stored-upgrader/Cargo.toml diff --git a/smart-contracts/contracts/test/do-nothing-stored-upgrader/src/main.rs b/smart_contracts/contracts/test/do-nothing-stored-upgrader/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/do-nothing-stored-upgrader/src/main.rs rename to smart_contracts/contracts/test/do-nothing-stored-upgrader/src/main.rs diff --git a/smart-contracts/contracts/test/do-nothing-stored/Cargo.toml b/smart_contracts/contracts/test/do-nothing-stored/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/do-nothing-stored/Cargo.toml rename to smart_contracts/contracts/test/do-nothing-stored/Cargo.toml diff --git a/smart-contracts/contracts/test/do-nothing-stored/src/main.rs b/smart_contracts/contracts/test/do-nothing-stored/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/do-nothing-stored/src/main.rs rename to smart_contracts/contracts/test/do-nothing-stored/src/main.rs diff --git a/smart-contracts/contracts/test/do-nothing/Cargo.toml b/smart_contracts/contracts/test/do-nothing/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/do-nothing/Cargo.toml rename to smart_contracts/contracts/test/do-nothing/Cargo.toml diff --git a/smart-contracts/contracts/test/do-nothing/src/main.rs b/smart_contracts/contracts/test/do-nothing/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/do-nothing/src/main.rs rename to smart_contracts/contracts/test/do-nothing/src/main.rs diff --git a/smart-contracts/contracts/test/ee-221-regression/Cargo.toml b/smart_contracts/contracts/test/ee-221-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-221-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-221-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-221-regression/src/main.rs b/smart_contracts/contracts/test/ee-221-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-221-regression/src/main.rs rename to smart_contracts/contracts/test/ee-221-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-401-regression-call/Cargo.toml b/smart_contracts/contracts/test/ee-401-regression-call/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-401-regression-call/Cargo.toml rename to smart_contracts/contracts/test/ee-401-regression-call/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-401-regression-call/src/main.rs b/smart_contracts/contracts/test/ee-401-regression-call/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-401-regression-call/src/main.rs rename to smart_contracts/contracts/test/ee-401-regression-call/src/main.rs diff --git a/smart-contracts/contracts/test/ee-401-regression/Cargo.toml b/smart_contracts/contracts/test/ee-401-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-401-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-401-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-401-regression/src/main.rs b/smart_contracts/contracts/test/ee-401-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-401-regression/src/main.rs rename to smart_contracts/contracts/test/ee-401-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-441-rng-state/Cargo.toml b/smart_contracts/contracts/test/ee-441-rng-state/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-441-rng-state/Cargo.toml rename to smart_contracts/contracts/test/ee-441-rng-state/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-441-rng-state/src/main.rs b/smart_contracts/contracts/test/ee-441-rng-state/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-441-rng-state/src/main.rs rename to smart_contracts/contracts/test/ee-441-rng-state/src/main.rs diff --git a/smart-contracts/contracts/test/ee-460-regression/Cargo.toml b/smart_contracts/contracts/test/ee-460-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-460-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-460-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-460-regression/src/main.rs b/smart_contracts/contracts/test/ee-460-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-460-regression/src/main.rs rename to smart_contracts/contracts/test/ee-460-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-532-regression/Cargo.toml b/smart_contracts/contracts/test/ee-532-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-532-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-532-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-532-regression/src/main.rs b/smart_contracts/contracts/test/ee-532-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-532-regression/src/main.rs rename to smart_contracts/contracts/test/ee-532-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-536-regression/Cargo.toml b/smart_contracts/contracts/test/ee-536-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-536-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-536-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-536-regression/src/main.rs b/smart_contracts/contracts/test/ee-536-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-536-regression/src/main.rs rename to smart_contracts/contracts/test/ee-536-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-539-regression/Cargo.toml b/smart_contracts/contracts/test/ee-539-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-539-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-539-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-539-regression/src/main.rs b/smart_contracts/contracts/test/ee-539-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-539-regression/src/main.rs rename to smart_contracts/contracts/test/ee-539-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-549-regression/Cargo.toml b/smart_contracts/contracts/test/ee-549-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-549-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-549-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-549-regression/src/main.rs b/smart_contracts/contracts/test/ee-549-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-549-regression/src/main.rs rename to smart_contracts/contracts/test/ee-549-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-550-regression/Cargo.toml b/smart_contracts/contracts/test/ee-550-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-550-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-550-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-550-regression/src/main.rs b/smart_contracts/contracts/test/ee-550-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-550-regression/src/main.rs rename to smart_contracts/contracts/test/ee-550-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-572-regression-create/Cargo.toml b/smart_contracts/contracts/test/ee-572-regression-create/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-572-regression-create/Cargo.toml rename to smart_contracts/contracts/test/ee-572-regression-create/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-572-regression-create/src/main.rs b/smart_contracts/contracts/test/ee-572-regression-create/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-572-regression-create/src/main.rs rename to smart_contracts/contracts/test/ee-572-regression-create/src/main.rs diff --git a/smart-contracts/contracts/test/ee-572-regression-escalate/Cargo.toml b/smart_contracts/contracts/test/ee-572-regression-escalate/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-572-regression-escalate/Cargo.toml rename to smart_contracts/contracts/test/ee-572-regression-escalate/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-572-regression-escalate/src/main.rs b/smart_contracts/contracts/test/ee-572-regression-escalate/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-572-regression-escalate/src/main.rs rename to smart_contracts/contracts/test/ee-572-regression-escalate/src/main.rs diff --git a/smart-contracts/contracts/test/ee-584-regression/Cargo.toml b/smart_contracts/contracts/test/ee-584-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-584-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-584-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-584-regression/src/main.rs b/smart_contracts/contracts/test/ee-584-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-584-regression/src/main.rs rename to smart_contracts/contracts/test/ee-584-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-597-regression/Cargo.toml b/smart_contracts/contracts/test/ee-597-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-597-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-597-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-597-regression/src/main.rs b/smart_contracts/contracts/test/ee-597-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-597-regression/src/main.rs rename to smart_contracts/contracts/test/ee-597-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-598-regression/Cargo.toml b/smart_contracts/contracts/test/ee-598-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-598-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-598-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-598-regression/src/main.rs b/smart_contracts/contracts/test/ee-598-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-598-regression/src/main.rs rename to smart_contracts/contracts/test/ee-598-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-599-regression/Cargo.toml b/smart_contracts/contracts/test/ee-599-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-599-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-599-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-599-regression/src/main.rs b/smart_contracts/contracts/test/ee-599-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-599-regression/src/main.rs rename to smart_contracts/contracts/test/ee-599-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-601-regression/Cargo.toml b/smart_contracts/contracts/test/ee-601-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-601-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-601-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-601-regression/src/main.rs b/smart_contracts/contracts/test/ee-601-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-601-regression/src/main.rs rename to smart_contracts/contracts/test/ee-601-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-771-regression/Cargo.toml b/smart_contracts/contracts/test/ee-771-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-771-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-771-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-771-regression/src/main.rs b/smart_contracts/contracts/test/ee-771-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-771-regression/src/main.rs rename to smart_contracts/contracts/test/ee-771-regression/src/main.rs diff --git a/smart-contracts/contracts/test/ee-803-regression/Cargo.toml b/smart_contracts/contracts/test/ee-803-regression/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/ee-803-regression/Cargo.toml rename to smart_contracts/contracts/test/ee-803-regression/Cargo.toml diff --git a/smart-contracts/contracts/test/ee-803-regression/src/main.rs b/smart_contracts/contracts/test/ee-803-regression/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/ee-803-regression/src/main.rs rename to smart_contracts/contracts/test/ee-803-regression/src/main.rs diff --git a/smart-contracts/contracts/test/endless-loop/Cargo.toml b/smart_contracts/contracts/test/endless-loop/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/endless-loop/Cargo.toml rename to smart_contracts/contracts/test/endless-loop/Cargo.toml diff --git a/smart-contracts/contracts/test/endless-loop/src/main.rs b/smart_contracts/contracts/test/endless-loop/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/endless-loop/src/main.rs rename to smart_contracts/contracts/test/endless-loop/src/main.rs diff --git a/smart-contracts/contracts/test/expensive-calculation/Cargo.toml b/smart_contracts/contracts/test/expensive-calculation/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/expensive-calculation/Cargo.toml rename to smart_contracts/contracts/test/expensive-calculation/Cargo.toml diff --git a/smart-contracts/contracts/test/expensive-calculation/src/main.rs b/smart_contracts/contracts/test/expensive-calculation/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/expensive-calculation/src/main.rs rename to smart_contracts/contracts/test/expensive-calculation/src/main.rs diff --git a/smart-contracts/contracts/test/get-arg/Cargo.toml b/smart_contracts/contracts/test/get-arg/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/get-arg/Cargo.toml rename to smart_contracts/contracts/test/get-arg/Cargo.toml diff --git a/smart-contracts/contracts/test/get-arg/src/main.rs b/smart_contracts/contracts/test/get-arg/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/get-arg/src/main.rs rename to smart_contracts/contracts/test/get-arg/src/main.rs diff --git a/smart-contracts/contracts/test/get-blocktime/Cargo.toml b/smart_contracts/contracts/test/get-blocktime/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/get-blocktime/Cargo.toml rename to smart_contracts/contracts/test/get-blocktime/Cargo.toml diff --git a/smart-contracts/contracts/test/get-blocktime/src/main.rs b/smart_contracts/contracts/test/get-blocktime/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/get-blocktime/src/main.rs rename to smart_contracts/contracts/test/get-blocktime/src/main.rs diff --git a/smart-contracts/contracts/test/get-caller-subcall/Cargo.toml b/smart_contracts/contracts/test/get-caller-subcall/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/get-caller-subcall/Cargo.toml rename to smart_contracts/contracts/test/get-caller-subcall/Cargo.toml diff --git a/smart-contracts/contracts/test/get-caller-subcall/src/main.rs b/smart_contracts/contracts/test/get-caller-subcall/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/get-caller-subcall/src/main.rs rename to smart_contracts/contracts/test/get-caller-subcall/src/main.rs diff --git a/smart-contracts/contracts/test/get-caller/Cargo.toml b/smart_contracts/contracts/test/get-caller/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/get-caller/Cargo.toml rename to smart_contracts/contracts/test/get-caller/Cargo.toml diff --git a/smart-contracts/contracts/test/get-caller/src/main.rs b/smart_contracts/contracts/test/get-caller/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/get-caller/src/main.rs rename to smart_contracts/contracts/test/get-caller/src/main.rs diff --git a/smart-contracts/contracts/test/get-phase-payment/Cargo.toml b/smart_contracts/contracts/test/get-phase-payment/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/get-phase-payment/Cargo.toml rename to smart_contracts/contracts/test/get-phase-payment/Cargo.toml diff --git a/smart-contracts/contracts/test/get-phase-payment/src/main.rs b/smart_contracts/contracts/test/get-phase-payment/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/get-phase-payment/src/main.rs rename to smart_contracts/contracts/test/get-phase-payment/src/main.rs diff --git a/smart-contracts/contracts/test/get-phase/Cargo.toml b/smart_contracts/contracts/test/get-phase/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/get-phase/Cargo.toml rename to smart_contracts/contracts/test/get-phase/Cargo.toml diff --git a/smart-contracts/contracts/test/get-phase/src/main.rs b/smart_contracts/contracts/test/get-phase/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/get-phase/src/main.rs rename to smart_contracts/contracts/test/get-phase/src/main.rs diff --git a/smart-contracts/contracts/test/groups/Cargo.toml b/smart_contracts/contracts/test/groups/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/groups/Cargo.toml rename to smart_contracts/contracts/test/groups/Cargo.toml diff --git a/smart-contracts/contracts/test/groups/src/main.rs b/smart_contracts/contracts/test/groups/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/groups/src/main.rs rename to smart_contracts/contracts/test/groups/src/main.rs diff --git a/smart-contracts/contracts/test/key-management-thresholds/Cargo.toml b/smart_contracts/contracts/test/key-management-thresholds/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/key-management-thresholds/Cargo.toml rename to smart_contracts/contracts/test/key-management-thresholds/Cargo.toml diff --git a/smart-contracts/contracts/test/key-management-thresholds/src/main.rs b/smart_contracts/contracts/test/key-management-thresholds/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/key-management-thresholds/src/main.rs rename to smart_contracts/contracts/test/key-management-thresholds/src/main.rs diff --git a/smart-contracts/contracts/test/list-named-keys/Cargo.toml b/smart_contracts/contracts/test/list-named-keys/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/list-named-keys/Cargo.toml rename to smart_contracts/contracts/test/list-named-keys/Cargo.toml diff --git a/smart-contracts/contracts/test/list-named-keys/src/main.rs b/smart_contracts/contracts/test/list-named-keys/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/list-named-keys/src/main.rs rename to smart_contracts/contracts/test/list-named-keys/src/main.rs diff --git a/smart-contracts/contracts/test/local-state-add/Cargo.toml b/smart_contracts/contracts/test/local-state-add/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/local-state-add/Cargo.toml rename to smart_contracts/contracts/test/local-state-add/Cargo.toml diff --git a/smart-contracts/contracts/test/local-state-add/src/main.rs b/smart_contracts/contracts/test/local-state-add/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/local-state-add/src/main.rs rename to smart_contracts/contracts/test/local-state-add/src/main.rs diff --git a/smart-contracts/contracts/test/local-state-stored-caller/Cargo.toml b/smart_contracts/contracts/test/local-state-stored-caller/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/local-state-stored-caller/Cargo.toml rename to smart_contracts/contracts/test/local-state-stored-caller/Cargo.toml diff --git a/smart-contracts/contracts/test/local-state-stored-caller/src/main.rs b/smart_contracts/contracts/test/local-state-stored-caller/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/local-state-stored-caller/src/main.rs rename to smart_contracts/contracts/test/local-state-stored-caller/src/main.rs diff --git a/smart-contracts/contracts/test/local-state-stored-upgraded/Cargo.toml b/smart_contracts/contracts/test/local-state-stored-upgraded/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/local-state-stored-upgraded/Cargo.toml rename to smart_contracts/contracts/test/local-state-stored-upgraded/Cargo.toml diff --git a/smart-contracts/contracts/test/local-state-stored-upgraded/src/bin/main.rs b/smart_contracts/contracts/test/local-state-stored-upgraded/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/test/local-state-stored-upgraded/src/bin/main.rs rename to smart_contracts/contracts/test/local-state-stored-upgraded/src/bin/main.rs diff --git a/smart-contracts/contracts/test/local-state-stored-upgraded/src/lib.rs b/smart_contracts/contracts/test/local-state-stored-upgraded/src/lib.rs similarity index 100% rename from smart-contracts/contracts/test/local-state-stored-upgraded/src/lib.rs rename to smart_contracts/contracts/test/local-state-stored-upgraded/src/lib.rs diff --git a/smart-contracts/contracts/test/local-state-stored-upgrader/Cargo.toml b/smart_contracts/contracts/test/local-state-stored-upgrader/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/local-state-stored-upgrader/Cargo.toml rename to smart_contracts/contracts/test/local-state-stored-upgrader/Cargo.toml diff --git a/smart-contracts/contracts/test/local-state-stored-upgrader/src/bin/main.rs b/smart_contracts/contracts/test/local-state-stored-upgrader/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/test/local-state-stored-upgrader/src/bin/main.rs rename to smart_contracts/contracts/test/local-state-stored-upgrader/src/bin/main.rs diff --git a/smart-contracts/contracts/test/local-state-stored/Cargo.toml b/smart_contracts/contracts/test/local-state-stored/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/local-state-stored/Cargo.toml rename to smart_contracts/contracts/test/local-state-stored/Cargo.toml diff --git a/smart-contracts/contracts/test/local-state-stored/src/main.rs b/smart_contracts/contracts/test/local-state-stored/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/local-state-stored/src/main.rs rename to smart_contracts/contracts/test/local-state-stored/src/main.rs diff --git a/smart-contracts/contracts/test/local-state/Cargo.toml b/smart_contracts/contracts/test/local-state/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/local-state/Cargo.toml rename to smart_contracts/contracts/test/local-state/Cargo.toml diff --git a/smart-contracts/contracts/test/local-state/src/bin/main.rs b/smart_contracts/contracts/test/local-state/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/test/local-state/src/bin/main.rs rename to smart_contracts/contracts/test/local-state/src/bin/main.rs diff --git a/smart-contracts/contracts/test/local-state/src/lib.rs b/smart_contracts/contracts/test/local-state/src/lib.rs similarity index 100% rename from smart-contracts/contracts/test/local-state/src/lib.rs rename to smart_contracts/contracts/test/local-state/src/lib.rs diff --git a/smart-contracts/contracts/test/main-purse/Cargo.toml b/smart_contracts/contracts/test/main-purse/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/main-purse/Cargo.toml rename to smart_contracts/contracts/test/main-purse/Cargo.toml diff --git a/smart-contracts/contracts/test/main-purse/src/main.rs b/smart_contracts/contracts/test/main-purse/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/main-purse/src/main.rs rename to smart_contracts/contracts/test/main-purse/src/main.rs diff --git a/smart-contracts/contracts/test/manage-groups/Cargo.toml b/smart_contracts/contracts/test/manage-groups/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/manage-groups/Cargo.toml rename to smart_contracts/contracts/test/manage-groups/Cargo.toml diff --git a/smart-contracts/contracts/test/manage-groups/src/main.rs b/smart_contracts/contracts/test/manage-groups/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/manage-groups/src/main.rs rename to smart_contracts/contracts/test/manage-groups/src/main.rs diff --git a/smart-contracts/contracts/test/measure-gas-subcall/Cargo.toml b/smart_contracts/contracts/test/measure-gas-subcall/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/measure-gas-subcall/Cargo.toml rename to smart_contracts/contracts/test/measure-gas-subcall/Cargo.toml diff --git a/smart-contracts/contracts/test/measure-gas-subcall/src/main.rs b/smart_contracts/contracts/test/measure-gas-subcall/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/measure-gas-subcall/src/main.rs rename to smart_contracts/contracts/test/measure-gas-subcall/src/main.rs diff --git a/smart-contracts/contracts/test/mint-purse/Cargo.toml b/smart_contracts/contracts/test/mint-purse/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/mint-purse/Cargo.toml rename to smart_contracts/contracts/test/mint-purse/Cargo.toml diff --git a/smart-contracts/contracts/test/mint-purse/src/main.rs b/smart_contracts/contracts/test/mint-purse/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/mint-purse/src/main.rs rename to smart_contracts/contracts/test/mint-purse/src/main.rs diff --git a/smart-contracts/contracts/test/modified-mint-caller/Cargo.toml b/smart_contracts/contracts/test/modified-mint-caller/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/modified-mint-caller/Cargo.toml rename to smart_contracts/contracts/test/modified-mint-caller/Cargo.toml diff --git a/smart-contracts/contracts/test/modified-mint-caller/src/main.rs b/smart_contracts/contracts/test/modified-mint-caller/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/modified-mint-caller/src/main.rs rename to smart_contracts/contracts/test/modified-mint-caller/src/main.rs diff --git a/smart-contracts/contracts/test/modified-mint-upgrader/Cargo.toml b/smart_contracts/contracts/test/modified-mint-upgrader/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/modified-mint-upgrader/Cargo.toml rename to smart_contracts/contracts/test/modified-mint-upgrader/Cargo.toml diff --git a/smart-contracts/contracts/test/modified-mint-upgrader/src/main.rs b/smart_contracts/contracts/test/modified-mint-upgrader/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/modified-mint-upgrader/src/main.rs rename to smart_contracts/contracts/test/modified-mint-upgrader/src/main.rs diff --git a/smart-contracts/contracts/test/modified-mint/Cargo.toml b/smart_contracts/contracts/test/modified-mint/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/modified-mint/Cargo.toml rename to smart_contracts/contracts/test/modified-mint/Cargo.toml diff --git a/smart-contracts/contracts/test/modified-mint/src/bin/main.rs b/smart_contracts/contracts/test/modified-mint/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/test/modified-mint/src/bin/main.rs rename to smart_contracts/contracts/test/modified-mint/src/bin/main.rs diff --git a/smart-contracts/contracts/test/modified-mint/src/lib.rs b/smart_contracts/contracts/test/modified-mint/src/lib.rs similarity index 100% rename from smart-contracts/contracts/test/modified-mint/src/lib.rs rename to smart_contracts/contracts/test/modified-mint/src/lib.rs diff --git a/smart-contracts/contracts/test/modified-system-upgrader/Cargo.toml b/smart_contracts/contracts/test/modified-system-upgrader/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/modified-system-upgrader/Cargo.toml rename to smart_contracts/contracts/test/modified-system-upgrader/Cargo.toml diff --git a/smart-contracts/contracts/test/modified-system-upgrader/src/main.rs b/smart_contracts/contracts/test/modified-system-upgrader/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/modified-system-upgrader/src/main.rs rename to smart_contracts/contracts/test/modified-system-upgrader/src/main.rs diff --git a/smart-contracts/contracts/test/named-keys/Cargo.toml b/smart_contracts/contracts/test/named-keys/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/named-keys/Cargo.toml rename to smart_contracts/contracts/test/named-keys/Cargo.toml diff --git a/smart-contracts/contracts/test/named-keys/src/main.rs b/smart_contracts/contracts/test/named-keys/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/named-keys/src/main.rs rename to smart_contracts/contracts/test/named-keys/src/main.rs diff --git a/smart-contracts/contracts/test/overwrite-uref-content/Cargo.toml b/smart_contracts/contracts/test/overwrite-uref-content/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/overwrite-uref-content/Cargo.toml rename to smart_contracts/contracts/test/overwrite-uref-content/Cargo.toml diff --git a/smart-contracts/contracts/test/overwrite-uref-content/src/main.rs b/smart_contracts/contracts/test/overwrite-uref-content/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/overwrite-uref-content/src/main.rs rename to smart_contracts/contracts/test/overwrite-uref-content/src/main.rs diff --git a/smart-contracts/contracts/test/pos-bonding/Cargo.toml b/smart_contracts/contracts/test/pos-bonding/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/pos-bonding/Cargo.toml rename to smart_contracts/contracts/test/pos-bonding/Cargo.toml diff --git a/smart-contracts/contracts/test/pos-bonding/src/main.rs b/smart_contracts/contracts/test/pos-bonding/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/pos-bonding/src/main.rs rename to smart_contracts/contracts/test/pos-bonding/src/main.rs diff --git a/smart-contracts/contracts/test/pos-finalize-payment/Cargo.toml b/smart_contracts/contracts/test/pos-finalize-payment/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/pos-finalize-payment/Cargo.toml rename to smart_contracts/contracts/test/pos-finalize-payment/Cargo.toml diff --git a/smart-contracts/contracts/test/pos-finalize-payment/src/main.rs b/smart_contracts/contracts/test/pos-finalize-payment/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/pos-finalize-payment/src/main.rs rename to smart_contracts/contracts/test/pos-finalize-payment/src/main.rs diff --git a/smart-contracts/contracts/test/pos-get-payment-purse/Cargo.toml b/smart_contracts/contracts/test/pos-get-payment-purse/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/pos-get-payment-purse/Cargo.toml rename to smart_contracts/contracts/test/pos-get-payment-purse/Cargo.toml diff --git a/smart-contracts/contracts/test/pos-get-payment-purse/src/main.rs b/smart_contracts/contracts/test/pos-get-payment-purse/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/pos-get-payment-purse/src/main.rs rename to smart_contracts/contracts/test/pos-get-payment-purse/src/main.rs diff --git a/smart-contracts/contracts/test/pos-refund-purse/Cargo.toml b/smart_contracts/contracts/test/pos-refund-purse/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/pos-refund-purse/Cargo.toml rename to smart_contracts/contracts/test/pos-refund-purse/Cargo.toml diff --git a/smart-contracts/contracts/test/pos-refund-purse/src/main.rs b/smart_contracts/contracts/test/pos-refund-purse/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/pos-refund-purse/src/main.rs rename to smart_contracts/contracts/test/pos-refund-purse/src/main.rs diff --git a/smart-contracts/contracts/test/purse-holder-stored-caller/Cargo.toml b/smart_contracts/contracts/test/purse-holder-stored-caller/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/purse-holder-stored-caller/Cargo.toml rename to smart_contracts/contracts/test/purse-holder-stored-caller/Cargo.toml diff --git a/smart-contracts/contracts/test/purse-holder-stored-caller/src/main.rs b/smart_contracts/contracts/test/purse-holder-stored-caller/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/purse-holder-stored-caller/src/main.rs rename to smart_contracts/contracts/test/purse-holder-stored-caller/src/main.rs diff --git a/smart-contracts/contracts/test/purse-holder-stored-upgrader/Cargo.toml b/smart_contracts/contracts/test/purse-holder-stored-upgrader/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/purse-holder-stored-upgrader/Cargo.toml rename to smart_contracts/contracts/test/purse-holder-stored-upgrader/Cargo.toml diff --git a/smart-contracts/contracts/test/purse-holder-stored-upgrader/src/main.rs b/smart_contracts/contracts/test/purse-holder-stored-upgrader/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/purse-holder-stored-upgrader/src/main.rs rename to smart_contracts/contracts/test/purse-holder-stored-upgrader/src/main.rs diff --git a/smart-contracts/contracts/test/purse-holder-stored/Cargo.toml b/smart_contracts/contracts/test/purse-holder-stored/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/purse-holder-stored/Cargo.toml rename to smart_contracts/contracts/test/purse-holder-stored/Cargo.toml diff --git a/smart-contracts/contracts/test/purse-holder-stored/src/main.rs b/smart_contracts/contracts/test/purse-holder-stored/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/purse-holder-stored/src/main.rs rename to smart_contracts/contracts/test/purse-holder-stored/src/main.rs diff --git a/smart-contracts/contracts/test/remove-associated-key/Cargo.toml b/smart_contracts/contracts/test/remove-associated-key/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/remove-associated-key/Cargo.toml rename to smart_contracts/contracts/test/remove-associated-key/Cargo.toml diff --git a/smart-contracts/contracts/test/remove-associated-key/src/main.rs b/smart_contracts/contracts/test/remove-associated-key/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/remove-associated-key/src/main.rs rename to smart_contracts/contracts/test/remove-associated-key/src/main.rs diff --git a/smart-contracts/contracts/test/test-payment-stored/Cargo.toml b/smart_contracts/contracts/test/test-payment-stored/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/test-payment-stored/Cargo.toml rename to smart_contracts/contracts/test/test-payment-stored/Cargo.toml diff --git a/smart-contracts/contracts/test/test-payment-stored/src/main.rs b/smart_contracts/contracts/test/test-payment-stored/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/test-payment-stored/src/main.rs rename to smart_contracts/contracts/test/test-payment-stored/src/main.rs diff --git a/smart-contracts/contracts/test/transfer-main-purse-to-new-purse/Cargo.toml b/smart_contracts/contracts/test/transfer-main-purse-to-new-purse/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/transfer-main-purse-to-new-purse/Cargo.toml rename to smart_contracts/contracts/test/transfer-main-purse-to-new-purse/Cargo.toml diff --git a/smart-contracts/contracts/test/transfer-main-purse-to-new-purse/src/main.rs b/smart_contracts/contracts/test/transfer-main-purse-to-new-purse/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/transfer-main-purse-to-new-purse/src/main.rs rename to smart_contracts/contracts/test/transfer-main-purse-to-new-purse/src/main.rs diff --git a/smart-contracts/contracts/test/transfer-main-purse-to-two-purses/Cargo.toml b/smart_contracts/contracts/test/transfer-main-purse-to-two-purses/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/transfer-main-purse-to-two-purses/Cargo.toml rename to smart_contracts/contracts/test/transfer-main-purse-to-two-purses/Cargo.toml diff --git a/smart-contracts/contracts/test/transfer-main-purse-to-two-purses/src/main.rs b/smart_contracts/contracts/test/transfer-main-purse-to-two-purses/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/transfer-main-purse-to-two-purses/src/main.rs rename to smart_contracts/contracts/test/transfer-main-purse-to-two-purses/src/main.rs diff --git a/smart-contracts/contracts/test/transfer-purse-to-account-stored/Cargo.toml b/smart_contracts/contracts/test/transfer-purse-to-account-stored/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/transfer-purse-to-account-stored/Cargo.toml rename to smart_contracts/contracts/test/transfer-purse-to-account-stored/Cargo.toml diff --git a/smart-contracts/contracts/test/transfer-purse-to-account-stored/src/main.rs b/smart_contracts/contracts/test/transfer-purse-to-account-stored/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/transfer-purse-to-account-stored/src/main.rs rename to smart_contracts/contracts/test/transfer-purse-to-account-stored/src/main.rs diff --git a/smart-contracts/contracts/test/transfer-purse-to-account/Cargo.toml b/smart_contracts/contracts/test/transfer-purse-to-account/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/transfer-purse-to-account/Cargo.toml rename to smart_contracts/contracts/test/transfer-purse-to-account/Cargo.toml diff --git a/smart-contracts/contracts/test/transfer-purse-to-account/src/bin/main.rs b/smart_contracts/contracts/test/transfer-purse-to-account/src/bin/main.rs similarity index 100% rename from smart-contracts/contracts/test/transfer-purse-to-account/src/bin/main.rs rename to smart_contracts/contracts/test/transfer-purse-to-account/src/bin/main.rs diff --git a/smart-contracts/contracts/test/transfer-purse-to-account/src/lib.rs b/smart_contracts/contracts/test/transfer-purse-to-account/src/lib.rs similarity index 100% rename from smart-contracts/contracts/test/transfer-purse-to-account/src/lib.rs rename to smart_contracts/contracts/test/transfer-purse-to-account/src/lib.rs diff --git a/smart-contracts/contracts/test/transfer-purse-to-purse/Cargo.toml b/smart_contracts/contracts/test/transfer-purse-to-purse/Cargo.toml similarity index 100% rename from smart-contracts/contracts/test/transfer-purse-to-purse/Cargo.toml rename to smart_contracts/contracts/test/transfer-purse-to-purse/Cargo.toml diff --git a/smart-contracts/contracts/test/transfer-purse-to-purse/src/main.rs b/smart_contracts/contracts/test/transfer-purse-to-purse/src/main.rs similarity index 100% rename from smart-contracts/contracts/test/transfer-purse-to-purse/src/main.rs rename to smart_contracts/contracts/test/transfer-purse-to-purse/src/main.rs diff --git a/smart-contracts/contracts-as/.gitignore b/smart_contracts/contracts_as/.gitignore similarity index 100% rename from smart-contracts/contracts-as/.gitignore rename to smart_contracts/contracts_as/.gitignore diff --git a/smart-contracts/contracts-as/client/bonding/assembly/index.ts b/smart_contracts/contracts_as/client/bonding/assembly/index.ts similarity index 72% rename from smart-contracts/contracts-as/client/bonding/assembly/index.ts rename to smart_contracts/contracts_as/client/bonding/assembly/index.ts index 2fa3f01c8d..fb219d5903 100644 --- a/smart-contracts/contracts-as/client/bonding/assembly/index.ts +++ b/smart_contracts/contracts_as/client/bonding/assembly/index.ts @@ -1,11 +1,11 @@ -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {U512} from "../../../../contract-as/assembly/bignum"; -import {CLValue} from "../../../../contract-as/assembly/clvalue"; -import {getMainPurse} from "../../../../contract-as/assembly/account"; -import {createPurse, transferFromPurseToPurse} from "../../../../contract-as/assembly/purse"; -import {RuntimeArgs} from "../../../../contract-as/assembly/runtime_args"; -import {Pair} from "../../../../contract-as/assembly/pair"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {U512} from "../../../../contract_as/assembly/bignum"; +import {CLValue} from "../../../../contract_as/assembly/clvalue"; +import {getMainPurse} from "../../../../contract_as/assembly/account"; +import {createPurse, transferFromPurseToPurse} from "../../../../contract_as/assembly/purse"; +import {RuntimeArgs} from "../../../../contract_as/assembly/runtime_args"; +import {Pair} from "../../../../contract_as/assembly/pair"; const POS_ACTION = "bond"; const ARG_AMOUNT = "amount"; diff --git a/smart-contracts/contracts-as/client/bonding/assembly/tsconfig.json b/smart_contracts/contracts_as/client/bonding/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/client/bonding/assembly/tsconfig.json rename to smart_contracts/contracts_as/client/bonding/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/client/bonding/index.js b/smart_contracts/contracts_as/client/bonding/index.js similarity index 100% rename from smart-contracts/contracts-as/client/bonding/index.js rename to smart_contracts/contracts_as/client/bonding/index.js diff --git a/smart-contracts/contracts-as/client/bonding/package.json b/smart_contracts/contracts_as/client/bonding/package.json similarity index 74% rename from smart-contracts/contracts-as/client/bonding/package.json rename to smart_contracts/contracts_as/client/bonding/package.json index 9c4a6164eb..147e969f04 100644 --- a/smart-contracts/contracts-as/client/bonding/package.json +++ b/smart_contracts/contracts_as/client/bonding/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/bonding.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/bonding.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/client/named-purse-payment/assembly/index.ts b/smart_contracts/contracts_as/client/named-purse-payment/assembly/index.ts similarity index 75% rename from smart-contracts/contracts-as/client/named-purse-payment/assembly/index.ts rename to smart_contracts/contracts_as/client/named-purse-payment/assembly/index.ts index 767a8f3788..ccc3798d23 100644 --- a/smart-contracts/contracts-as/client/named-purse-payment/assembly/index.ts +++ b/smart_contracts/contracts_as/client/named-purse-payment/assembly/index.ts @@ -1,14 +1,14 @@ -import * as CL from "../../../../contract-as/assembly"; -import {getKey} from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {CLValue} from "../../../../contract-as/assembly/clvalue"; -import {U512} from "../../../../contract-as/assembly/bignum"; -import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; -import {URef} from "../../../../contract-as/assembly/uref"; -import {KeyVariant} from "../../../../contract-as/assembly/key"; -import {transferFromPurseToPurse} from "../../../../contract-as/assembly/purse"; -import {RuntimeArgs} from "../../../../contract-as/assembly/runtime_args"; -import {Pair} from "../../../../contract-as/assembly/pair"; +import * as CL from "../../../../contract_as/assembly"; +import {getKey} from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {CLValue} from "../../../../contract_as/assembly/clvalue"; +import {U512} from "../../../../contract_as/assembly/bignum"; +import {fromBytesString} from "../../../../contract_as/assembly/bytesrepr"; +import {URef} from "../../../../contract_as/assembly/uref"; +import {KeyVariant} from "../../../../contract_as/assembly/key"; +import {transferFromPurseToPurse} from "../../../../contract_as/assembly/purse"; +import {RuntimeArgs} from "../../../../contract_as/assembly/runtime_args"; +import {Pair} from "../../../../contract_as/assembly/pair"; const GET_PAYMENT_PURSE = "get_payment_purse"; const SET_REFUND_PURSE= "set_refund_purse"; diff --git a/smart-contracts/contracts-as/client/named-purse-payment/assembly/tsconfig.json b/smart_contracts/contracts_as/client/named-purse-payment/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/client/named-purse-payment/assembly/tsconfig.json rename to smart_contracts/contracts_as/client/named-purse-payment/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/client/named-purse-payment/index.js b/smart_contracts/contracts_as/client/named-purse-payment/index.js similarity index 100% rename from smart-contracts/contracts-as/client/named-purse-payment/index.js rename to smart_contracts/contracts_as/client/named-purse-payment/index.js diff --git a/smart-contracts/contracts-as/test/purse-holder-stored/package.json b/smart_contracts/contracts_as/client/named-purse-payment/package.json similarity index 75% rename from smart-contracts/contracts-as/test/purse-holder-stored/package.json rename to smart_contracts/contracts_as/client/named-purse-payment/package.json index 2eeac8f7de..38ac6a8333 100644 --- a/smart-contracts/contracts-as/test/purse-holder-stored/package.json +++ b/smart_contracts/contracts_as/client/named-purse-payment/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/purse_holder_stored.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/named_purse_payment.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/client/revert/assembly/index.ts b/smart_contracts/contracts_as/client/revert/assembly/index.ts similarity index 54% rename from smart-contracts/contracts-as/client/revert/assembly/index.ts rename to smart_contracts/contracts_as/client/revert/assembly/index.ts index 9aa47988e2..df92f91419 100644 --- a/smart-contracts/contracts-as/client/revert/assembly/index.ts +++ b/smart_contracts/contracts_as/client/revert/assembly/index.ts @@ -1,4 +1,4 @@ -import {Error} from "../../../../contract-as/assembly/error"; +import {Error} from "../../../../contract_as/assembly/error"; export function call(): void { Error.fromUserError(100).revert(); diff --git a/smart-contracts/contracts-as/client/revert/assembly/tsconfig.json b/smart_contracts/contracts_as/client/revert/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/client/revert/assembly/tsconfig.json rename to smart_contracts/contracts_as/client/revert/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/client/revert/index.js b/smart_contracts/contracts_as/client/revert/index.js similarity index 100% rename from smart-contracts/contracts-as/client/revert/index.js rename to smart_contracts/contracts_as/client/revert/index.js diff --git a/smart-contracts/contracts-as/test/groups/package.json b/smart_contracts/contracts_as/client/revert/package.json similarity index 74% rename from smart-contracts/contracts-as/test/groups/package.json rename to smart_contracts/contracts_as/client/revert/package.json index b7625c1c89..1321c53d2d 100644 --- a/smart-contracts/contracts-as/test/groups/package.json +++ b/smart_contracts/contracts_as/client/revert/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/groups.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/revert.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/index.ts b/smart_contracts/contracts_as/client/transfer-to-account-u512/assembly/index.ts similarity index 72% rename from smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/index.ts rename to smart_contracts/contracts_as/client/transfer-to-account-u512/assembly/index.ts index fe1d05bd53..a25b3b589b 100644 --- a/smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/index.ts +++ b/smart_contracts/contracts_as/client/transfer-to-account-u512/assembly/index.ts @@ -1,8 +1,8 @@ -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {U512} from "../../../../contract-as/assembly/bignum"; -import {getMainPurse} from "../../../../contract-as/assembly/account"; -import {transferFromPurseToAccount, TransferredTo} from "../../../../contract-as/assembly/purse"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {U512} from "../../../../contract_as/assembly/bignum"; +import {getMainPurse} from "../../../../contract_as/assembly/account"; +import {transferFromPurseToAccount, TransferredTo} from "../../../../contract_as/assembly/purse"; const ARG_TARGET = "target"; const ARG_AMOUNT = "amount"; diff --git a/smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/tsconfig.json b/smart_contracts/contracts_as/client/transfer-to-account-u512/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/client/transfer-to-account-u512/assembly/tsconfig.json rename to smart_contracts/contracts_as/client/transfer-to-account-u512/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/client/transfer-to-account-u512/index.js b/smart_contracts/contracts_as/client/transfer-to-account-u512/index.js similarity index 100% rename from smart-contracts/contracts-as/client/transfer-to-account-u512/index.js rename to smart_contracts/contracts_as/client/transfer-to-account-u512/index.js diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-caller/package.json b/smart_contracts/contracts_as/client/transfer-to-account-u512/package.json similarity index 76% rename from smart-contracts/contracts-as/test/do-nothing-stored-caller/package.json rename to smart_contracts/contracts_as/client/transfer-to-account-u512/package.json index 620b28cda3..2a229aa996 100644 --- a/smart-contracts/contracts-as/test/do-nothing-stored-caller/package.json +++ b/smart_contracts/contracts_as/client/transfer-to-account-u512/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/do_nothing_stored_caller.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/transfer_to_account_u512.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/client/unbonding/assembly/index.ts b/smart_contracts/contracts_as/client/unbonding/assembly/index.ts similarity index 66% rename from smart-contracts/contracts-as/client/unbonding/assembly/index.ts rename to smart_contracts/contracts_as/client/unbonding/assembly/index.ts index bc173d8566..42978046c9 100644 --- a/smart-contracts/contracts-as/client/unbonding/assembly/index.ts +++ b/smart_contracts/contracts_as/client/unbonding/assembly/index.ts @@ -1,9 +1,9 @@ -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {U512} from "../../../../contract-as/assembly/bignum"; -import {CLValue} from "../../../../contract-as/assembly/clvalue"; -import {RuntimeArgs} from "../../../../contract-as/assembly/runtime_args"; -import {Pair} from "../../../../contract-as/assembly/pair"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {U512} from "../../../../contract_as/assembly/bignum"; +import {CLValue} from "../../../../contract_as/assembly/clvalue"; +import {RuntimeArgs} from "../../../../contract_as/assembly/runtime_args"; +import {Pair} from "../../../../contract_as/assembly/pair"; const POS_ACTION = "unbond"; const ARG_AMOUNT = "amount"; diff --git a/smart-contracts/contracts-as/client/unbonding/assembly/tsconfig.json b/smart_contracts/contracts_as/client/unbonding/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/client/unbonding/assembly/tsconfig.json rename to smart_contracts/contracts_as/client/unbonding/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/client/unbonding/index.js b/smart_contracts/contracts_as/client/unbonding/index.js similarity index 100% rename from smart-contracts/contracts-as/client/unbonding/index.js rename to smart_contracts/contracts_as/client/unbonding/index.js diff --git a/smart-contracts/contracts-as/test/get-phase/package.json b/smart_contracts/contracts_as/client/unbonding/package.json similarity index 75% rename from smart-contracts/contracts-as/test/get-phase/package.json rename to smart_contracts/contracts_as/client/unbonding/package.json index 9213f8d144..861d5ec41c 100644 --- a/smart-contracts/contracts-as/test/get-phase/package.json +++ b/smart_contracts/contracts_as/client/unbonding/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/get_phase.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/unbonding.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/add-update-associated-key/assembly/index.ts b/smart_contracts/contracts_as/test/add-update-associated-key/assembly/index.ts similarity index 72% rename from smart-contracts/contracts-as/test/add-update-associated-key/assembly/index.ts rename to smart_contracts/contracts_as/test/add-update-associated-key/assembly/index.ts index 32dfd60e85..38c6e3b94c 100644 --- a/smart-contracts/contracts-as/test/add-update-associated-key/assembly/index.ts +++ b/smart_contracts/contracts_as/test/add-update-associated-key/assembly/index.ts @@ -1,9 +1,9 @@ // The entry file of your WebAssembly module. -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {addAssociatedKey, AddKeyFailure, updateAssociatedKey, UpdateKeyFailure} from "../../../../contract-as/assembly/account"; -import {typedToArray} from "../../../../contract-as/assembly/utils"; -import {AccountHash} from "../../../../contract-as/assembly/key"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {addAssociatedKey, AddKeyFailure, updateAssociatedKey, UpdateKeyFailure} from "../../../../contract_as/assembly/account"; +import {typedToArray} from "../../../../contract_as/assembly/utils"; +import {AccountHash} from "../../../../contract_as/assembly/key"; const INIT_WEIGHT: u8 = 1; diff --git a/smart-contracts/contracts-as/test/add-update-associated-key/assembly/tsconfig.json b/smart_contracts/contracts_as/test/add-update-associated-key/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/add-update-associated-key/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/add-update-associated-key/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/add-update-associated-key/index.js b/smart_contracts/contracts_as/test/add-update-associated-key/index.js similarity index 100% rename from smart-contracts/contracts-as/test/add-update-associated-key/index.js rename to smart_contracts/contracts_as/test/add-update-associated-key/index.js diff --git a/smart-contracts/contracts-as/test/add-update-associated-key/package.json b/smart_contracts/contracts_as/test/add-update-associated-key/package.json similarity index 76% rename from smart-contracts/contracts-as/test/add-update-associated-key/package.json rename to smart_contracts/contracts_as/test/add-update-associated-key/package.json index 6896674e2c..d633d779a0 100644 --- a/smart-contracts/contracts-as/test/add-update-associated-key/package.json +++ b/smart_contracts/contracts_as/test/add-update-associated-key/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/add_update_associated_key.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/add_update_associated_key.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/authorized-keys/assembly/index.ts b/smart_contracts/contracts_as/test/authorized-keys/assembly/index.ts similarity index 80% rename from smart-contracts/contracts-as/test/authorized-keys/assembly/index.ts rename to smart_contracts/contracts_as/test/authorized-keys/assembly/index.ts index 69d826c0bc..afbb02dddd 100644 --- a/smart-contracts/contracts-as/test/authorized-keys/assembly/index.ts +++ b/smart_contracts/contracts_as/test/authorized-keys/assembly/index.ts @@ -1,9 +1,9 @@ -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {fromBytesString, fromBytesI32} from "../../../../contract-as/assembly/bytesrepr"; -import {arrayToTyped} from "../../../../contract-as/assembly/utils"; -import {Key, AccountHash} from "../../../../contract-as/assembly/key" -import {addAssociatedKey, AddKeyFailure, ActionType, setActionThreshold, SetThresholdFailure} from "../../../../contract-as/assembly/account"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {fromBytesString, fromBytesI32} from "../../../../contract_as/assembly/bytesrepr"; +import {arrayToTyped} from "../../../../contract_as/assembly/utils"; +import {Key, AccountHash} from "../../../../contract_as/assembly/key" +import {addAssociatedKey, AddKeyFailure, ActionType, setActionThreshold, SetThresholdFailure} from "../../../../contract_as/assembly/account"; const ARG_KEY_MANAGEMENT_THRESHOLD = "key_management_threshold"; const ARG_DEPLOY_THRESHOLD = "deploy_threshold"; diff --git a/smart-contracts/contracts-as/test/authorized-keys/assembly/tsconfig.json b/smart_contracts/contracts_as/test/authorized-keys/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/authorized-keys/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/authorized-keys/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/authorized-keys/index.js b/smart_contracts/contracts_as/test/authorized-keys/index.js similarity index 100% rename from smart-contracts/contracts-as/test/authorized-keys/index.js rename to smart_contracts/contracts_as/test/authorized-keys/index.js diff --git a/smart-contracts/contracts-as/test/create-purse-01/package.json b/smart_contracts/contracts_as/test/authorized-keys/package.json similarity index 75% rename from smart-contracts/contracts-as/test/create-purse-01/package.json rename to smart_contracts/contracts_as/test/authorized-keys/package.json index 4b76e52432..2123be0925 100644 --- a/smart-contracts/contracts-as/test/create-purse-01/package.json +++ b/smart_contracts/contracts_as/test/authorized-keys/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/create_purse_01.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/authorized_keys.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/create-purse-01/assembly/index.ts b/smart_contracts/contracts_as/test/create-purse-01/assembly/index.ts similarity index 57% rename from smart-contracts/contracts-as/test/create-purse-01/assembly/index.ts rename to smart_contracts/contracts_as/test/create-purse-01/assembly/index.ts index cd0718eb90..88adb2c318 100644 --- a/smart-contracts/contracts-as/test/create-purse-01/assembly/index.ts +++ b/smart_contracts/contracts_as/test/create-purse-01/assembly/index.ts @@ -1,11 +1,11 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; -import {Key} from "../../../../contract-as/assembly/key"; -import {putKey} from "../../../../contract-as/assembly"; -import {createPurse} from "../../../../contract-as/assembly/purse"; -import {URef} from "../../../../contract-as/assembly/uref"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {fromBytesString} from "../../../../contract_as/assembly/bytesrepr"; +import {Key} from "../../../../contract_as/assembly/key"; +import {putKey} from "../../../../contract_as/assembly"; +import {createPurse} from "../../../../contract_as/assembly/purse"; +import {URef} from "../../../../contract_as/assembly/uref"; const ARG_PURSE_NAME = "purse_name"; diff --git a/smart-contracts/contracts-as/test/create-purse-01/assembly/tsconfig.json b/smart_contracts/contracts_as/test/create-purse-01/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/create-purse-01/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/create-purse-01/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/create-purse-01/index.js b/smart_contracts/contracts_as/test/create-purse-01/index.js similarity index 100% rename from smart-contracts/contracts-as/test/create-purse-01/index.js rename to smart_contracts/contracts_as/test/create-purse-01/index.js diff --git a/smart-contracts/contracts-as/test/authorized-keys/package.json b/smart_contracts/contracts_as/test/create-purse-01/package.json similarity index 75% rename from smart-contracts/contracts-as/test/authorized-keys/package.json rename to smart_contracts/contracts_as/test/create-purse-01/package.json index 46f3cb0479..63488c0eb3 100644 --- a/smart-contracts/contracts-as/test/authorized-keys/package.json +++ b/smart_contracts/contracts_as/test/create-purse-01/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/authorized_keys.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/create_purse_01.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/index.ts b/smart_contracts/contracts_as/test/do-nothing-stored-caller/assembly/index.ts similarity index 66% rename from smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/index.ts rename to smart_contracts/contracts_as/test/do-nothing-stored-caller/assembly/index.ts index 822ef08d36..902430532b 100644 --- a/smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/index.ts +++ b/smart_contracts/contracts_as/test/do-nothing-stored-caller/assembly/index.ts @@ -1,11 +1,11 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {fromBytesString, toBytesU32} from "../../../../contract-as/assembly/bytesrepr"; -import {CLValue} from "../../../../contract-as/assembly/clvalue"; -import {RuntimeArgs} from "../../../../contract-as/assembly/runtime_args"; -import {Pair} from "../../../../contract-as/assembly/pair"; -import {Option} from "../../../../contract-as/assembly/option"; -import {arrayToTyped} from "../../../../contract-as/assembly/utils"; +import * as CL from "../../../../contract_as/assembly"; +import {fromBytesString, toBytesU32} from "../../../../contract_as/assembly/bytesrepr"; +import {CLValue} from "../../../../contract_as/assembly/clvalue"; +import {RuntimeArgs} from "../../../../contract_as/assembly/runtime_args"; +import {Pair} from "../../../../contract_as/assembly/pair"; +import {Option} from "../../../../contract_as/assembly/option"; +import {arrayToTyped} from "../../../../contract_as/assembly/utils"; const ENTRY_FUNCTION_NAME = "delegate"; const PURSE_NAME_ARG_NAME = "purse_name"; diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/tsconfig.json b/smart_contracts/contracts_as/test/do-nothing-stored-caller/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/do-nothing-stored-caller/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/do-nothing-stored-caller/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-caller/index.js b/smart_contracts/contracts_as/test/do-nothing-stored-caller/index.js similarity index 100% rename from smart-contracts/contracts-as/test/do-nothing-stored-caller/index.js rename to smart_contracts/contracts_as/test/do-nothing-stored-caller/index.js diff --git a/smart-contracts/contracts-as/client/transfer-to-account-u512/package.json b/smart_contracts/contracts_as/test/do-nothing-stored-caller/package.json similarity index 76% rename from smart-contracts/contracts-as/client/transfer-to-account-u512/package.json rename to smart_contracts/contracts_as/test/do-nothing-stored-caller/package.json index 04281b677b..ebe91da71d 100644 --- a/smart-contracts/contracts-as/client/transfer-to-account-u512/package.json +++ b/smart_contracts/contracts_as/test/do-nothing-stored-caller/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/transfer_to_account_u512.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/do_nothing_stored_caller.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/index.ts b/smart_contracts/contracts_as/test/do-nothing-stored-upgrader/assembly/index.ts similarity index 71% rename from smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/index.ts rename to smart_contracts/contracts_as/test/do-nothing-stored-upgrader/assembly/index.ts index 7a6c811ed9..4e2fbae288 100644 --- a/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/index.ts +++ b/smart_contracts/contracts_as/test/do-nothing-stored-upgrader/assembly/index.ts @@ -1,13 +1,13 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; -import {Key} from "../../../../contract-as/assembly/key"; -import {Pair} from "../../../../contract-as/assembly/pair"; -import {URef} from "../../../../contract-as/assembly/uref"; -import {Pair} from "../../../../contract-as/assembly/pair"; -import {createPurse} from "../../../../contract-as/assembly/purse"; -import {CLType, CLTypeTag} from "../../../../contract-as/assembly/clvalue"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {fromBytesString} from "../../../../contract_as/assembly/bytesrepr"; +import {Key} from "../../../../contract_as/assembly/key"; +import {Pair} from "../../../../contract_as/assembly/pair"; +import {URef} from "../../../../contract_as/assembly/uref"; +import {Pair} from "../../../../contract_as/assembly/pair"; +import {createPurse} from "../../../../contract_as/assembly/purse"; +import {CLType, CLTypeTag} from "../../../../contract_as/assembly/clvalue"; import * as CreatePurse01 from "../../create-purse-01/assembly"; const ENTRY_FUNCTION_NAME = "delegate"; diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/tsconfig.json b/smart_contracts/contracts_as/test/do-nothing-stored-upgrader/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/do-nothing-stored-upgrader/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/do-nothing-stored-upgrader/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/index.js b/smart_contracts/contracts_as/test/do-nothing-stored-upgrader/index.js similarity index 100% rename from smart-contracts/contracts-as/test/do-nothing-stored-upgrader/index.js rename to smart_contracts/contracts_as/test/do-nothing-stored-upgrader/index.js diff --git a/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/package.json b/smart_contracts/contracts_as/test/do-nothing-stored-upgrader/package.json similarity index 76% rename from smart-contracts/contracts-as/test/do-nothing-stored-upgrader/package.json rename to smart_contracts/contracts_as/test/do-nothing-stored-upgrader/package.json index 3437cd988e..9baf276a41 100644 --- a/smart-contracts/contracts-as/test/do-nothing-stored-upgrader/package.json +++ b/smart_contracts/contracts_as/test/do-nothing-stored-upgrader/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/do_nothing_stored_upgrader.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/do_nothing_stored_upgrader.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/do-nothing-stored/assembly/index.ts b/smart_contracts/contracts_as/test/do-nothing-stored/assembly/index.ts similarity index 72% rename from smart-contracts/contracts-as/test/do-nothing-stored/assembly/index.ts rename to smart_contracts/contracts_as/test/do-nothing-stored/assembly/index.ts index b3882fb6a4..d3d7ce29a0 100644 --- a/smart-contracts/contracts-as/test/do-nothing-stored/assembly/index.ts +++ b/smart_contracts/contracts_as/test/do-nothing-stored/assembly/index.ts @@ -1,10 +1,10 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {fromBytesString, toBytesMap} from "../../../../contract-as/assembly/bytesrepr"; -import {Key} from "../../../../contract-as/assembly/key"; -import {CLValue, CLType, CLTypeTag} from "../../../../contract-as/assembly/clvalue"; -import {Pair} from "../../../../contract-as/assembly/pair"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {fromBytesString, toBytesMap} from "../../../../contract_as/assembly/bytesrepr"; +import {Key} from "../../../../contract_as/assembly/key"; +import {CLValue, CLType, CLTypeTag} from "../../../../contract_as/assembly/clvalue"; +import {Pair} from "../../../../contract_as/assembly/pair"; const ENTRY_FUNCTION_NAME = "delegate"; const HASH_KEY_NAME = "do_nothing_hash"; diff --git a/smart-contracts/contracts-as/test/do-nothing-stored/assembly/tsconfig.json b/smart_contracts/contracts_as/test/do-nothing-stored/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/do-nothing-stored/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/do-nothing-stored/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/do-nothing-stored/index.js b/smart_contracts/contracts_as/test/do-nothing-stored/index.js similarity index 100% rename from smart-contracts/contracts-as/test/do-nothing-stored/index.js rename to smart_contracts/contracts_as/test/do-nothing-stored/index.js diff --git a/smart-contracts/contracts-as/test/do-nothing-stored/package.json b/smart_contracts/contracts_as/test/do-nothing-stored/package.json similarity index 75% rename from smart-contracts/contracts-as/test/do-nothing-stored/package.json rename to smart_contracts/contracts_as/test/do-nothing-stored/package.json index b0c189bcd4..0da74cfb26 100644 --- a/smart-contracts/contracts-as/test/do-nothing-stored/package.json +++ b/smart_contracts/contracts_as/test/do-nothing-stored/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/do_nothing_stored.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/do_nothing_stored.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/do-nothing/assembly/index.ts b/smart_contracts/contracts_as/test/do-nothing/assembly/index.ts similarity index 100% rename from smart-contracts/contracts-as/test/do-nothing/assembly/index.ts rename to smart_contracts/contracts_as/test/do-nothing/assembly/index.ts diff --git a/smart-contracts/contracts-as/test/do-nothing/assembly/tsconfig.json b/smart_contracts/contracts_as/test/do-nothing/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/do-nothing/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/do-nothing/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/do-nothing/index.js b/smart_contracts/contracts_as/test/do-nothing/index.js similarity index 100% rename from smart-contracts/contracts-as/test/do-nothing/index.js rename to smart_contracts/contracts_as/test/do-nothing/index.js diff --git a/smart-contracts/contracts-as/test/get-caller/package.json b/smart_contracts/contracts_as/test/do-nothing/package.json similarity index 75% rename from smart-contracts/contracts-as/test/get-caller/package.json rename to smart_contracts/contracts_as/test/do-nothing/package.json index d1a9ae7dfb..160d9f5507 100644 --- a/smart-contracts/contracts-as/test/get-caller/package.json +++ b/smart_contracts/contracts_as/test/do-nothing/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/get_caller.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/do_nothing.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/endless-loop/assembly/index.ts b/smart_contracts/contracts_as/test/endless-loop/assembly/index.ts similarity index 55% rename from smart-contracts/contracts-as/test/endless-loop/assembly/index.ts rename to smart_contracts/contracts_as/test/endless-loop/assembly/index.ts index 5cbbc6f0ec..6a97b18237 100644 --- a/smart-contracts/contracts-as/test/endless-loop/assembly/index.ts +++ b/smart_contracts/contracts_as/test/endless-loop/assembly/index.ts @@ -1,4 +1,4 @@ -import {getMainPurse} from "../../../../contract-as/assembly/account"; +import {getMainPurse} from "../../../../contract_as/assembly/account"; export function call(): void { while(true){ diff --git a/smart-contracts/contracts-as/test/endless-loop/assembly/tsconfig.json b/smart_contracts/contracts_as/test/endless-loop/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/endless-loop/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/endless-loop/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/endless-loop/index.js b/smart_contracts/contracts_as/test/endless-loop/index.js similarity index 100% rename from smart-contracts/contracts-as/test/endless-loop/index.js rename to smart_contracts/contracts_as/test/endless-loop/index.js diff --git a/smart-contracts/contracts-as/test/endless-loop/package.json b/smart_contracts/contracts_as/test/endless-loop/package.json similarity index 75% rename from smart-contracts/contracts-as/test/endless-loop/package.json rename to smart_contracts/contracts_as/test/endless-loop/package.json index f11f6a6f7a..4349827a65 100644 --- a/smart-contracts/contracts-as/test/endless-loop/package.json +++ b/smart_contracts/contracts_as/test/endless-loop/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/endless_loop.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/endless_loop.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/get-arg/assembly/index.ts b/smart_contracts/contracts_as/test/get-arg/assembly/index.ts similarity index 78% rename from smart-contracts/contracts-as/test/get-arg/assembly/index.ts rename to smart_contracts/contracts_as/test/get-arg/assembly/index.ts index 827831fb70..e172f278dc 100644 --- a/smart-contracts/contracts-as/test/get-arg/assembly/index.ts +++ b/smart_contracts/contracts_as/test/get-arg/assembly/index.ts @@ -1,8 +1,8 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {U512} from "../../../../contract-as/assembly/bignum"; -import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {U512} from "../../../../contract_as/assembly/bignum"; +import {fromBytesString} from "../../../../contract_as/assembly/bytesrepr"; const EXPECTED_STRING = "Hello, world!"; const EXPECTED_NUM = 42; diff --git a/smart-contracts/contracts-as/test/get-arg/assembly/tsconfig.json b/smart_contracts/contracts_as/test/get-arg/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/get-arg/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/get-arg/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/get-arg/index.js b/smart_contracts/contracts_as/test/get-arg/index.js similarity index 100% rename from smart-contracts/contracts-as/test/get-arg/index.js rename to smart_contracts/contracts_as/test/get-arg/index.js diff --git a/smart-contracts/contracts-as/test/get-arg/package.json b/smart_contracts/contracts_as/test/get-arg/package.json similarity index 74% rename from smart-contracts/contracts-as/test/get-arg/package.json rename to smart_contracts/contracts_as/test/get-arg/package.json index 2dd97ec23f..280b387ee6 100644 --- a/smart-contracts/contracts-as/test/get-arg/package.json +++ b/smart_contracts/contracts_as/test/get-arg/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/get_arg.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/get_arg.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/get-blocktime/assembly/index.ts b/smart_contracts/contracts_as/test/get-blocktime/assembly/index.ts similarity index 69% rename from smart-contracts/contracts-as/test/get-blocktime/assembly/index.ts rename to smart_contracts/contracts_as/test/get-blocktime/assembly/index.ts index 5ec1668b78..c9c1bdbec7 100644 --- a/smart-contracts/contracts-as/test/get-blocktime/assembly/index.ts +++ b/smart_contracts/contracts_as/test/get-blocktime/assembly/index.ts @@ -1,6 +1,6 @@ -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {fromBytesU64} from "../../../../contract-as/assembly/bytesrepr"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {fromBytesU64} from "../../../../contract_as/assembly/bytesrepr"; const ARG_KNOWN_BLOCK_TIME = "known_block_time"; diff --git a/smart-contracts/contracts-as/test/get-blocktime/assembly/tsconfig.json b/smart_contracts/contracts_as/test/get-blocktime/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/get-blocktime/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/get-blocktime/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/get-blocktime/index.js b/smart_contracts/contracts_as/test/get-blocktime/index.js similarity index 100% rename from smart-contracts/contracts-as/test/get-blocktime/index.js rename to smart_contracts/contracts_as/test/get-blocktime/index.js diff --git a/smart-contracts/contracts-as/test/get-blocktime/package.json b/smart_contracts/contracts_as/test/get-blocktime/package.json similarity index 75% rename from smart-contracts/contracts-as/test/get-blocktime/package.json rename to smart_contracts/contracts_as/test/get-blocktime/package.json index 997cafd0d0..07dbb6a565 100644 --- a/smart-contracts/contracts-as/test/get-blocktime/package.json +++ b/smart_contracts/contracts_as/test/get-blocktime/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/get_blocktime.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/get_blocktime.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/get-caller/assembly/index.ts b/smart_contracts/contracts_as/test/get-caller/assembly/index.ts similarity index 71% rename from smart-contracts/contracts-as/test/get-caller/assembly/index.ts rename to smart_contracts/contracts_as/test/get-caller/assembly/index.ts index cea609365f..b191efa951 100644 --- a/smart-contracts/contracts-as/test/get-caller/assembly/index.ts +++ b/smart_contracts/contracts_as/test/get-caller/assembly/index.ts @@ -1,7 +1,7 @@ -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {typedToArray, checkArraysEqual} from "../../../../contract-as/assembly/utils"; -import {AccountHash} from "../../../../contract-as/assembly/key"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {typedToArray, checkArraysEqual} from "../../../../contract_as/assembly/utils"; +import {AccountHash} from "../../../../contract_as/assembly/key"; const ARG_ACCOUNT = "account"; diff --git a/smart-contracts/contracts-as/test/get-caller/assembly/tsconfig.json b/smart_contracts/contracts_as/test/get-caller/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/get-caller/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/get-caller/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/get-caller/index.js b/smart_contracts/contracts_as/test/get-caller/index.js similarity index 100% rename from smart-contracts/contracts-as/test/get-caller/index.js rename to smart_contracts/contracts_as/test/get-caller/index.js diff --git a/smart-contracts/contracts-as/test/do-nothing/package.json b/smart_contracts/contracts_as/test/get-caller/package.json similarity index 75% rename from smart-contracts/contracts-as/test/do-nothing/package.json rename to smart_contracts/contracts_as/test/get-caller/package.json index a9780e9b0f..88581728f9 100644 --- a/smart-contracts/contracts-as/test/do-nothing/package.json +++ b/smart_contracts/contracts_as/test/get-caller/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/do_nothing.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/get_caller.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/get-phase-payment/assembly/index.ts b/smart_contracts/contracts_as/test/get-phase-payment/assembly/index.ts similarity index 72% rename from smart-contracts/contracts-as/test/get-phase-payment/assembly/index.ts rename to smart_contracts/contracts_as/test/get-phase-payment/assembly/index.ts index 3e102a2012..bb5f57639e 100644 --- a/smart-contracts/contracts-as/test/get-phase-payment/assembly/index.ts +++ b/smart_contracts/contracts_as/test/get-phase-payment/assembly/index.ts @@ -1,11 +1,11 @@ // The entry file of your WebAssembly module. -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {U512} from "../../../../contract-as/assembly/bignum"; -import {URef} from "../../../../contract-as/assembly/uref"; -import {RuntimeArgs} from "../../../../contract-as/assembly/runtime_args"; -import {getMainPurse} from "../../../../contract-as/assembly/account"; -import {transferFromPurseToPurse} from "../../../../contract-as/assembly/purse"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {U512} from "../../../../contract_as/assembly/bignum"; +import {URef} from "../../../../contract_as/assembly/uref"; +import {RuntimeArgs} from "../../../../contract_as/assembly/runtime_args"; +import {getMainPurse} from "../../../../contract_as/assembly/account"; +import {transferFromPurseToPurse} from "../../../../contract_as/assembly/purse"; const ARG_PHASE = "phase"; const ARG_AMOUNT = "amount"; diff --git a/smart-contracts/contracts-as/test/get-phase-payment/assembly/tsconfig.json b/smart_contracts/contracts_as/test/get-phase-payment/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/get-phase-payment/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/get-phase-payment/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/get-phase-payment/index.js b/smart_contracts/contracts_as/test/get-phase-payment/index.js similarity index 100% rename from smart-contracts/contracts-as/test/get-phase-payment/index.js rename to smart_contracts/contracts_as/test/get-phase-payment/index.js diff --git a/smart-contracts/contracts-as/test/get-phase-payment/package.json b/smart_contracts/contracts_as/test/get-phase-payment/package.json similarity index 75% rename from smart-contracts/contracts-as/test/get-phase-payment/package.json rename to smart_contracts/contracts_as/test/get-phase-payment/package.json index aca27f67e1..753385cdda 100644 --- a/smart-contracts/contracts-as/test/get-phase-payment/package.json +++ b/smart_contracts/contracts_as/test/get-phase-payment/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/get_phase_payment.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/get_phase_payment.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/get-phase/assembly/index.ts b/smart_contracts/contracts_as/test/get-phase/assembly/index.ts similarity index 73% rename from smart-contracts/contracts-as/test/get-phase/assembly/index.ts rename to smart_contracts/contracts_as/test/get-phase/assembly/index.ts index 37a58741ce..75e08c83e4 100644 --- a/smart-contracts/contracts-as/test/get-phase/assembly/index.ts +++ b/smart_contracts/contracts_as/test/get-phase/assembly/index.ts @@ -1,5 +1,5 @@ -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; const ARG_PHASE = "phase"; diff --git a/smart-contracts/contracts-as/test/get-phase/assembly/tsconfig.json b/smart_contracts/contracts_as/test/get-phase/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/get-phase/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/get-phase/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/get-phase/index.js b/smart_contracts/contracts_as/test/get-phase/index.js similarity index 100% rename from smart-contracts/contracts-as/test/get-phase/index.js rename to smart_contracts/contracts_as/test/get-phase/index.js diff --git a/smart-contracts/contracts-as/client/unbonding/package.json b/smart_contracts/contracts_as/test/get-phase/package.json similarity index 75% rename from smart-contracts/contracts-as/client/unbonding/package.json rename to smart_contracts/contracts_as/test/get-phase/package.json index 4578eb3638..9d4502ed40 100644 --- a/smart-contracts/contracts-as/client/unbonding/package.json +++ b/smart_contracts/contracts_as/test/get-phase/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/unbonding.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/get_phase.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/groups/assembly/index.ts b/smart_contracts/contracts_as/test/groups/assembly/index.ts similarity index 93% rename from smart-contracts/contracts-as/test/groups/assembly/index.ts rename to smart_contracts/contracts_as/test/groups/assembly/index.ts index 120761982b..b2160d9059 100644 --- a/smart-contracts/contracts-as/test/groups/assembly/index.ts +++ b/smart_contracts/contracts_as/test/groups/assembly/index.ts @@ -1,14 +1,14 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import { Error, ErrorCode } from "../../../../contract-as/assembly/error"; -import { Key } from "../../../../contract-as/assembly/key"; -import { URef } from "../../../../contract-as/assembly/uref"; -import { CLValue, CLType, CLTypeTag } from "../../../../contract-as/assembly/clvalue"; -import { Pair } from "../../../../contract-as/assembly/pair"; -import { RuntimeArgs } from "../../../../contract-as/assembly/runtime_args"; -import { Option } from "../../../../contract-as/assembly/option"; -import { toBytesU32 } from "../../../../contract-as/assembly/bytesrepr"; -import { arrayToTyped } from "../../../../contract-as/assembly/utils"; +import * as CL from "../../../../contract_as/assembly"; +import { Error, ErrorCode } from "../../../../contract_as/assembly/error"; +import { Key } from "../../../../contract_as/assembly/key"; +import { URef } from "../../../../contract_as/assembly/uref"; +import { CLValue, CLType, CLTypeTag } from "../../../../contract_as/assembly/clvalue"; +import { Pair } from "../../../../contract_as/assembly/pair"; +import { RuntimeArgs } from "../../../../contract_as/assembly/runtime_args"; +import { Option } from "../../../../contract_as/assembly/option"; +import { toBytesU32 } from "../../../../contract_as/assembly/bytesrepr"; +import { arrayToTyped } from "../../../../contract_as/assembly/utils"; const CONTRACT_INITIAL_VERSION: u8 = 1; const PACKAGE_HASH_KEY = "package_hash_key"; diff --git a/smart-contracts/contracts-as/test/groups/assembly/tsconfig.json b/smart_contracts/contracts_as/test/groups/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/groups/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/groups/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/groups/index.js b/smart_contracts/contracts_as/test/groups/index.js similarity index 100% rename from smart-contracts/contracts-as/test/groups/index.js rename to smart_contracts/contracts_as/test/groups/index.js diff --git a/smart-contracts/contracts-as/client/revert/package.json b/smart_contracts/contracts_as/test/groups/package.json similarity index 74% rename from smart-contracts/contracts-as/client/revert/package.json rename to smart_contracts/contracts_as/test/groups/package.json index 06088a4c17..9eeeca5801 100644 --- a/smart-contracts/contracts-as/client/revert/package.json +++ b/smart_contracts/contracts_as/test/groups/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/revert.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/groups.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/key-management-thresholds/assembly/index.ts b/smart_contracts/contracts_as/test/key-management-thresholds/assembly/index.ts similarity index 91% rename from smart-contracts/contracts-as/test/key-management-thresholds/assembly/index.ts rename to smart_contracts/contracts_as/test/key-management-thresholds/assembly/index.ts index 831c4fa686..ecc8e5025a 100644 --- a/smart-contracts/contracts-as/test/key-management-thresholds/assembly/index.ts +++ b/smart_contracts/contracts_as/test/key-management-thresholds/assembly/index.ts @@ -1,12 +1,12 @@ -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; -import {arrayToTyped} from "../../../../contract-as/assembly/utils"; -import {AccountHash} from "../../../../contract-as/assembly/key"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {fromBytesString} from "../../../../contract_as/assembly/bytesrepr"; +import {arrayToTyped} from "../../../../contract_as/assembly/utils"; +import {AccountHash} from "../../../../contract_as/assembly/key"; import {addAssociatedKey, AddKeyFailure, setActionThreshold, ActionType, SetThresholdFailure, updateAssociatedKey, UpdateKeyFailure, - removeAssociatedKey, RemoveKeyFailure} from "../../../../contract-as/assembly/account"; + removeAssociatedKey, RemoveKeyFailure} from "../../../../contract_as/assembly/account"; const ARG_STAGE = "stage"; diff --git a/smart-contracts/contracts-as/test/key-management-thresholds/assembly/tsconfig.json b/smart_contracts/contracts_as/test/key-management-thresholds/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/key-management-thresholds/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/key-management-thresholds/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/key-management-thresholds/index.js b/smart_contracts/contracts_as/test/key-management-thresholds/index.js similarity index 100% rename from smart-contracts/contracts-as/test/key-management-thresholds/index.js rename to smart_contracts/contracts_as/test/key-management-thresholds/index.js diff --git a/smart-contracts/contracts-as/test/key-management-thresholds/package.json b/smart_contracts/contracts_as/test/key-management-thresholds/package.json similarity index 76% rename from smart-contracts/contracts-as/test/key-management-thresholds/package.json rename to smart_contracts/contracts_as/test/key-management-thresholds/package.json index 2b19cab0d1..d9f5813f49 100644 --- a/smart-contracts/contracts-as/test/key-management-thresholds/package.json +++ b/smart_contracts/contracts_as/test/key-management-thresholds/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/key_management_thresholds.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/key_management_thresholds.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/list-named-keys/assembly/index.ts b/smart_contracts/contracts_as/test/list-named-keys/assembly/index.ts similarity index 90% rename from smart-contracts/contracts-as/test/list-named-keys/assembly/index.ts rename to smart_contracts/contracts_as/test/list-named-keys/assembly/index.ts index 982ba47d0a..aa16c8cd20 100644 --- a/smart-contracts/contracts-as/test/list-named-keys/assembly/index.ts +++ b/smart_contracts/contracts_as/test/list-named-keys/assembly/index.ts @@ -1,9 +1,9 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {fromBytesMap, fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; -import {Key} from "../../../../contract-as/assembly/key"; -import {checkItemsEqual} from "../../../../contract-as/assembly/utils"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {fromBytesMap, fromBytesString} from "../../../../contract_as/assembly/bytesrepr"; +import {Key} from "../../../../contract_as/assembly/key"; +import {checkItemsEqual} from "../../../../contract_as/assembly/utils"; const ARG_INITIAL_NAMED_KEYS = "initial_named_args"; const ARG_NEW_NAMED_KEYS = "new_named_keys"; diff --git a/smart-contracts/contracts-as/test/list-named-keys/assembly/tsconfig.json b/smart_contracts/contracts_as/test/list-named-keys/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/list-named-keys/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/list-named-keys/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/list-named-keys/index.js b/smart_contracts/contracts_as/test/list-named-keys/index.js similarity index 100% rename from smart-contracts/contracts-as/test/list-named-keys/index.js rename to smart_contracts/contracts_as/test/list-named-keys/index.js diff --git a/smart-contracts/contracts-as/test/list-named-keys/package.json b/smart_contracts/contracts_as/test/list-named-keys/package.json similarity index 75% rename from smart-contracts/contracts-as/test/list-named-keys/package.json rename to smart_contracts/contracts_as/test/list-named-keys/package.json index f269ea1f40..1f56511aba 100644 --- a/smart-contracts/contracts-as/test/list-named-keys/package.json +++ b/smart_contracts/contracts_as/test/list-named-keys/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/list_named_keys.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/list_named_keys.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/main-purse/assembly/index.ts b/smart_contracts/contracts_as/test/main-purse/assembly/index.ts similarity index 73% rename from smart-contracts/contracts-as/test/main-purse/assembly/index.ts rename to smart_contracts/contracts_as/test/main-purse/assembly/index.ts index e970780d9e..7db7ebfc8d 100644 --- a/smart-contracts/contracts-as/test/main-purse/assembly/index.ts +++ b/smart_contracts/contracts_as/test/main-purse/assembly/index.ts @@ -1,8 +1,8 @@ //@ts-nocheck -import {getMainPurse} from "../../../../contract-as/assembly/account"; -import * as CL from "../../../../contract-as/assembly"; -import {Error} from "../../../../contract-as/assembly/error"; -import {URef} from "../../../../contract-as/assembly/uref"; +import {getMainPurse} from "../../../../contract_as/assembly/account"; +import * as CL from "../../../../contract_as/assembly"; +import {Error} from "../../../../contract_as/assembly/error"; +import {URef} from "../../../../contract_as/assembly/uref"; const ARG_PURSE = "purse"; diff --git a/smart-contracts/contracts-as/test/main-purse/assembly/tsconfig.json b/smart_contracts/contracts_as/test/main-purse/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/main-purse/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/main-purse/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/main-purse/index.js b/smart_contracts/contracts_as/test/main-purse/index.js similarity index 100% rename from smart-contracts/contracts-as/test/main-purse/index.js rename to smart_contracts/contracts_as/test/main-purse/index.js diff --git a/smart-contracts/contracts-as/test/main-purse/package.json b/smart_contracts/contracts_as/test/main-purse/package.json similarity index 75% rename from smart-contracts/contracts-as/test/main-purse/package.json rename to smart_contracts/contracts_as/test/main-purse/package.json index 197f7c50a8..0b62733af7 100644 --- a/smart-contracts/contracts-as/test/main-purse/package.json +++ b/smart_contracts/contracts_as/test/main-purse/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/main_purse.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/main_purse.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/manage-groups/assembly/index.ts b/smart_contracts/contracts_as/test/manage-groups/assembly/index.ts similarity index 93% rename from smart-contracts/contracts-as/test/manage-groups/assembly/index.ts rename to smart_contracts/contracts_as/test/manage-groups/assembly/index.ts index 21ad0c2495..7af9391279 100644 --- a/smart-contracts/contracts-as/test/manage-groups/assembly/index.ts +++ b/smart_contracts/contracts_as/test/manage-groups/assembly/index.ts @@ -1,12 +1,12 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import { Error, ErrorCode } from "../../../../contract-as/assembly/error"; -import { Key } from "../../../../contract-as/assembly/key"; -import { URef } from "../../../../contract-as/assembly/uref"; -import { fromBytesString, fromBytesU64, Result, fromBytesArray } from "../../../../contract-as/assembly/bytesrepr"; -import { CLValue, CLType, CLTypeTag } from "../../../../contract-as/assembly/clvalue"; -import { Pair } from "../../../../contract-as/assembly/pair"; -import { RuntimeArgs } from "../../../../contract-as/assembly/runtime_args"; +import * as CL from "../../../../contract_as/assembly"; +import { Error, ErrorCode } from "../../../../contract_as/assembly/error"; +import { Key } from "../../../../contract_as/assembly/key"; +import { URef } from "../../../../contract_as/assembly/uref"; +import { fromBytesString, fromBytesU64, Result, fromBytesArray } from "../../../../contract_as/assembly/bytesrepr"; +import { CLValue, CLType, CLTypeTag } from "../../../../contract_as/assembly/clvalue"; +import { Pair } from "../../../../contract_as/assembly/pair"; +import { RuntimeArgs } from "../../../../contract_as/assembly/runtime_args"; const PACKAGE_HASH_KEY = "package_hash_key"; const PACKAGE_ACCESS_KEY = "package_access_key"; diff --git a/smart-contracts/contracts-as/test/manage-groups/assembly/tsconfig.json b/smart_contracts/contracts_as/test/manage-groups/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/manage-groups/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/manage-groups/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/manage-groups/index.js b/smart_contracts/contracts_as/test/manage-groups/index.js similarity index 100% rename from smart-contracts/contracts-as/test/manage-groups/index.js rename to smart_contracts/contracts_as/test/manage-groups/index.js diff --git a/smart-contracts/contracts-as/test/manage-groups/package.json b/smart_contracts/contracts_as/test/manage-groups/package.json similarity index 75% rename from smart-contracts/contracts-as/test/manage-groups/package.json rename to smart_contracts/contracts_as/test/manage-groups/package.json index 60b4f8c9f1..062c708c6e 100644 --- a/smart-contracts/contracts-as/test/manage-groups/package.json +++ b/smart_contracts/contracts_as/test/manage-groups/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/manage_groups.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/manage_groups.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/named-keys/assembly/index.ts b/smart_contracts/contracts_as/test/named-keys/assembly/index.ts similarity index 93% rename from smart-contracts/contracts-as/test/named-keys/assembly/index.ts rename to smart_contracts/contracts_as/test/named-keys/assembly/index.ts index bf65f5b3df..70d53f58ed 100644 --- a/smart-contracts/contracts-as/test/named-keys/assembly/index.ts +++ b/smart_contracts/contracts_as/test/named-keys/assembly/index.ts @@ -1,9 +1,9 @@ -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {Key} from "../../../../contract-as/assembly/key"; -import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; -import {U512} from "../../../../contract-as/assembly/bignum"; -import {CLValue} from "../../../../contract-as/assembly/clvalue"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {Key} from "../../../../contract_as/assembly/key"; +import {fromBytesString} from "../../../../contract_as/assembly/bytesrepr"; +import {U512} from "../../../../contract_as/assembly/bignum"; +import {CLValue} from "../../../../contract_as/assembly/clvalue"; const COMMAND_CREATE_UREF1 = "create-uref1"; const COMMAND_CREATE_UREF2 = "create-uref2"; diff --git a/smart-contracts/contracts-as/test/named-keys/assembly/tsconfig.json b/smart_contracts/contracts_as/test/named-keys/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/named-keys/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/named-keys/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/named-keys/index.js b/smart_contracts/contracts_as/test/named-keys/index.js similarity index 100% rename from smart-contracts/contracts-as/test/named-keys/index.js rename to smart_contracts/contracts_as/test/named-keys/index.js diff --git a/smart-contracts/contracts-as/test/named-keys/package.json b/smart_contracts/contracts_as/test/named-keys/package.json similarity index 75% rename from smart-contracts/contracts-as/test/named-keys/package.json rename to smart_contracts/contracts_as/test/named-keys/package.json index 5494c8b8ea..8442b7070e 100644 --- a/smart-contracts/contracts-as/test/named-keys/package.json +++ b/smart_contracts/contracts_as/test/named-keys/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/named_keys.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/named_keys.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/overwrite-uref-content/assembly/index.ts b/smart_contracts/contracts_as/test/overwrite-uref-content/assembly/index.ts similarity index 68% rename from smart-contracts/contracts-as/test/overwrite-uref-content/assembly/index.ts rename to smart_contracts/contracts_as/test/overwrite-uref-content/assembly/index.ts index ecb59b116d..1dc22460a6 100644 --- a/smart-contracts/contracts-as/test/overwrite-uref-content/assembly/index.ts +++ b/smart_contracts/contracts_as/test/overwrite-uref-content/assembly/index.ts @@ -1,8 +1,8 @@ -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {AccessRights, URef} from "../../../../contract-as/assembly/uref"; -import {Key} from "../../../../contract-as/assembly/key"; -import {CLValue} from "../../../../contract-as/assembly/clvalue"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {AccessRights, URef} from "../../../../contract_as/assembly/uref"; +import {Key} from "../../../../contract_as/assembly/key"; +import {CLValue} from "../../../../contract_as/assembly/clvalue"; const ARG_CONTRACT_UREF = "contract_uref"; const REPLACEMENT_DATA = "bawitdaba"; diff --git a/smart-contracts/contracts-as/test/overwrite-uref-content/assembly/tsconfig.json b/smart_contracts/contracts_as/test/overwrite-uref-content/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/overwrite-uref-content/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/overwrite-uref-content/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/overwrite-uref-content/index.js b/smart_contracts/contracts_as/test/overwrite-uref-content/index.js similarity index 100% rename from smart-contracts/contracts-as/test/overwrite-uref-content/index.js rename to smart_contracts/contracts_as/test/overwrite-uref-content/index.js diff --git a/smart-contracts/contracts-as/test/overwrite-uref-content/package.json b/smart_contracts/contracts_as/test/overwrite-uref-content/package.json similarity index 76% rename from smart-contracts/contracts-as/test/overwrite-uref-content/package.json rename to smart_contracts/contracts_as/test/overwrite-uref-content/package.json index ae2eb0b0c2..047163da53 100644 --- a/smart-contracts/contracts-as/test/overwrite-uref-content/package.json +++ b/smart_contracts/contracts_as/test/overwrite-uref-content/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/overwrite_uref_content.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/overwrite_uref_content.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/index.ts b/smart_contracts/contracts_as/test/purse-holder-stored-caller/assembly/index.ts similarity index 77% rename from smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/index.ts rename to smart_contracts/contracts_as/test/purse-holder-stored-caller/assembly/index.ts index 51dfe28dfb..aaaac9fa30 100644 --- a/smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/index.ts +++ b/smart_contracts/contracts_as/test/purse-holder-stored-caller/assembly/index.ts @@ -1,12 +1,12 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error} from "../../../../contract-as/assembly/error"; -import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; -import {Key} from "../../../../contract-as/assembly/key"; -import {putKey} from "../../../../contract-as/assembly"; -import {CLValue} from "../../../../contract-as/assembly/clvalue"; -import { RuntimeArgs } from "../../../../contract-as/assembly/runtime_args"; -import {Pair} from "../../../../contract-as/assembly/pair"; +import * as CL from "../../../../contract_as/assembly"; +import {Error} from "../../../../contract_as/assembly/error"; +import {fromBytesString} from "../../../../contract_as/assembly/bytesrepr"; +import {Key} from "../../../../contract_as/assembly/key"; +import {putKey} from "../../../../contract_as/assembly"; +import {CLValue} from "../../../../contract_as/assembly/clvalue"; +import { RuntimeArgs } from "../../../../contract_as/assembly/runtime_args"; +import {Pair} from "../../../../contract_as/assembly/pair"; const METHOD_VERSION = "version"; const HASH_KEY_NAME = "purse_holder"; diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/tsconfig.json b/smart_contracts/contracts_as/test/purse-holder-stored-caller/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/purse-holder-stored-caller/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/purse-holder-stored-caller/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-caller/index.js b/smart_contracts/contracts_as/test/purse-holder-stored-caller/index.js similarity index 100% rename from smart-contracts/contracts-as/test/purse-holder-stored-caller/index.js rename to smart_contracts/contracts_as/test/purse-holder-stored-caller/index.js diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-caller/package.json b/smart_contracts/contracts_as/test/purse-holder-stored-caller/package.json similarity index 76% rename from smart-contracts/contracts-as/test/purse-holder-stored-caller/package.json rename to smart_contracts/contracts_as/test/purse-holder-stored-caller/package.json index 011b8f5aa5..20041ea6f5 100644 --- a/smart-contracts/contracts-as/test/purse-holder-stored-caller/package.json +++ b/smart_contracts/contracts_as/test/purse-holder-stored-caller/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/purse_holder_stored_caller.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/purse_holder_stored_caller.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/index.ts b/smart_contracts/contracts_as/test/purse-holder-stored-upgrader/assembly/index.ts similarity index 83% rename from smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/index.ts rename to smart_contracts/contracts_as/test/purse-holder-stored-upgrader/assembly/index.ts index 2e1afc51b3..79ed8c5503 100644 --- a/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/index.ts +++ b/smart_contracts/contracts_as/test/purse-holder-stored-upgrader/assembly/index.ts @@ -1,13 +1,13 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; -import {Key} from "../../../../contract-as/assembly/key"; -import {CLValue, CLType, CLTypeTag} from "../../../../contract-as/assembly/clvalue"; -import {URef} from "../../../../contract-as/assembly/uref"; -import {createPurse} from "../../../../contract-as/assembly/purse"; -import { checkItemsEqual } from "../../../../contract-as/assembly/utils"; -import {Pair} from "../../../../contract-as/assembly/pair"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {fromBytesString} from "../../../../contract_as/assembly/bytesrepr"; +import {Key} from "../../../../contract_as/assembly/key"; +import {CLValue, CLType, CLTypeTag} from "../../../../contract_as/assembly/clvalue"; +import {URef} from "../../../../contract_as/assembly/uref"; +import {createPurse} from "../../../../contract_as/assembly/purse"; +import { checkItemsEqual } from "../../../../contract_as/assembly/utils"; +import {Pair} from "../../../../contract_as/assembly/pair"; const METHOD_ADD = "add"; const METHOD_REMOVE = "remove"; diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/tsconfig.json b/smart_contracts/contracts_as/test/purse-holder-stored-upgrader/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/purse-holder-stored-upgrader/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/purse-holder-stored-upgrader/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/index.js b/smart_contracts/contracts_as/test/purse-holder-stored-upgrader/index.js similarity index 100% rename from smart-contracts/contracts-as/test/purse-holder-stored-upgrader/index.js rename to smart_contracts/contracts_as/test/purse-holder-stored-upgrader/index.js diff --git a/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/package.json b/smart_contracts/contracts_as/test/purse-holder-stored-upgrader/package.json similarity index 76% rename from smart-contracts/contracts-as/test/purse-holder-stored-upgrader/package.json rename to smart_contracts/contracts_as/test/purse-holder-stored-upgrader/package.json index 483a454df1..13383d334a 100644 --- a/smart-contracts/contracts-as/test/purse-holder-stored-upgrader/package.json +++ b/smart_contracts/contracts_as/test/purse-holder-stored-upgrader/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/purse_holder_stored_upgrader.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/purse_holder_stored_upgrader.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/purse-holder-stored/assembly/index.ts b/smart_contracts/contracts_as/test/purse-holder-stored/assembly/index.ts similarity index 76% rename from smart-contracts/contracts-as/test/purse-holder-stored/assembly/index.ts rename to smart_contracts/contracts_as/test/purse-holder-stored/assembly/index.ts index a1553ffe91..6f0ab394ba 100644 --- a/smart-contracts/contracts-as/test/purse-holder-stored/assembly/index.ts +++ b/smart_contracts/contracts_as/test/purse-holder-stored/assembly/index.ts @@ -1,14 +1,14 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {fromBytesString, toBytesMap} from "../../../../contract-as/assembly/bytesrepr"; -import {Key} from "../../../../contract-as/assembly/key"; -import {Pair} from "../../../../contract-as/assembly/pair"; -import {putKey, ret} from "../../../../contract-as/assembly"; -import {CLValue, CLType, CLTypeTag} from "../../../../contract-as/assembly/clvalue"; -import {createPurse} from "../../../../contract-as/assembly/purse"; -import {URef} from "../../../../contract-as/assembly/uref"; -import {CLTypeTag} from "../../../../contract-as/assembly/clvalue"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {fromBytesString, toBytesMap} from "../../../../contract_as/assembly/bytesrepr"; +import {Key} from "../../../../contract_as/assembly/key"; +import {Pair} from "../../../../contract_as/assembly/pair"; +import {putKey, ret} from "../../../../contract_as/assembly"; +import {CLValue, CLType, CLTypeTag} from "../../../../contract_as/assembly/clvalue"; +import {createPurse} from "../../../../contract_as/assembly/purse"; +import {URef} from "../../../../contract_as/assembly/uref"; +import {CLTypeTag} from "../../../../contract_as/assembly/clvalue"; const METHOD_ADD = "add"; const METHOD_REMOVE = "remove"; diff --git a/smart-contracts/contracts-as/test/purse-holder-stored/assembly/tsconfig.json b/smart_contracts/contracts_as/test/purse-holder-stored/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/purse-holder-stored/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/purse-holder-stored/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/purse-holder-stored/index.js b/smart_contracts/contracts_as/test/purse-holder-stored/index.js similarity index 100% rename from smart-contracts/contracts-as/test/purse-holder-stored/index.js rename to smart_contracts/contracts_as/test/purse-holder-stored/index.js diff --git a/smart-contracts/contracts-as/client/named-purse-payment/package.json b/smart_contracts/contracts_as/test/purse-holder-stored/package.json similarity index 75% rename from smart-contracts/contracts-as/client/named-purse-payment/package.json rename to smart_contracts/contracts_as/test/purse-holder-stored/package.json index 86c75bd4b5..0a91065cab 100644 --- a/smart-contracts/contracts-as/client/named-purse-payment/package.json +++ b/smart_contracts/contracts_as/test/purse-holder-stored/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/named_purse_payment.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/purse_holder_stored.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/remove-associated-key/assembly/index.ts b/smart_contracts/contracts_as/test/remove-associated-key/assembly/index.ts similarity index 67% rename from smart-contracts/contracts-as/test/remove-associated-key/assembly/index.ts rename to smart_contracts/contracts_as/test/remove-associated-key/assembly/index.ts index c1d04465f4..da1363f92d 100644 --- a/smart-contracts/contracts-as/test/remove-associated-key/assembly/index.ts +++ b/smart_contracts/contracts_as/test/remove-associated-key/assembly/index.ts @@ -1,9 +1,9 @@ // The entry file of your WebAssembly module. -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {removeAssociatedKey, RemoveKeyFailure} from "../../../../contract-as/assembly/account"; -import {typedToArray} from "../../../../contract-as/assembly/utils"; -import {AccountHash} from "../../../../contract-as/assembly/key"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {removeAssociatedKey, RemoveKeyFailure} from "../../../../contract_as/assembly/account"; +import {typedToArray} from "../../../../contract_as/assembly/utils"; +import {AccountHash} from "../../../../contract_as/assembly/key"; const ARG_ACCOUNT = "account"; diff --git a/smart-contracts/contracts-as/test/remove-associated-key/assembly/tsconfig.json b/smart_contracts/contracts_as/test/remove-associated-key/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/remove-associated-key/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/remove-associated-key/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/remove-associated-key/index.js b/smart_contracts/contracts_as/test/remove-associated-key/index.js similarity index 100% rename from smart-contracts/contracts-as/test/remove-associated-key/index.js rename to smart_contracts/contracts_as/test/remove-associated-key/index.js diff --git a/smart-contracts/contracts-as/test/remove-associated-key/package.json b/smart_contracts/contracts_as/test/remove-associated-key/package.json similarity index 76% rename from smart-contracts/contracts-as/test/remove-associated-key/package.json rename to smart_contracts/contracts_as/test/remove-associated-key/package.json index c130320ec9..917830fd4c 100644 --- a/smart-contracts/contracts-as/test/remove-associated-key/package.json +++ b/smart_contracts/contracts_as/test/remove-associated-key/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/remove_associated_key.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/remove_associated_key.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/index.ts b/smart_contracts/contracts_as/test/transfer-main-purse-to-new-purse/assembly/index.ts similarity index 71% rename from smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/index.ts rename to smart_contracts/contracts_as/test/transfer-main-purse-to-new-purse/assembly/index.ts index ef8cb4bea5..0faf030887 100644 --- a/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/index.ts +++ b/smart_contracts/contracts_as/test/transfer-main-purse-to-new-purse/assembly/index.ts @@ -1,13 +1,13 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error} from "../../../../contract-as/assembly/error"; -import {U512} from "../../../../contract-as/assembly/bignum"; -import {Key} from "../../../../contract-as/assembly/key"; -import {URef} from "../../../../contract-as/assembly/uref"; -import {putKey} from "../../../../contract-as/assembly"; -import {getMainPurse} from "../../../../contract-as/assembly/account"; -import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; -import {createPurse, transferFromPurseToPurse} from "../../../../contract-as/assembly/purse"; +import * as CL from "../../../../contract_as/assembly"; +import {Error} from "../../../../contract_as/assembly/error"; +import {U512} from "../../../../contract_as/assembly/bignum"; +import {Key} from "../../../../contract_as/assembly/key"; +import {URef} from "../../../../contract_as/assembly/uref"; +import {putKey} from "../../../../contract_as/assembly"; +import {getMainPurse} from "../../../../contract_as/assembly/account"; +import {fromBytesString} from "../../../../contract_as/assembly/bytesrepr"; +import {createPurse, transferFromPurseToPurse} from "../../../../contract_as/assembly/purse"; const ARG_AMOUNT = "amount"; diff --git a/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/tsconfig.json b/smart_contracts/contracts_as/test/transfer-main-purse-to-new-purse/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/transfer-main-purse-to-new-purse/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/index.js b/smart_contracts/contracts_as/test/transfer-main-purse-to-new-purse/index.js similarity index 100% rename from smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/index.js rename to smart_contracts/contracts_as/test/transfer-main-purse-to-new-purse/index.js diff --git a/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/package.json b/smart_contracts/contracts_as/test/transfer-main-purse-to-new-purse/package.json similarity index 77% rename from smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/package.json rename to smart_contracts/contracts_as/test/transfer-main-purse-to-new-purse/package.json index 6c652b2f99..5a955d5dce 100644 --- a/smart-contracts/contracts-as/test/transfer-main-purse-to-new-purse/package.json +++ b/smart_contracts/contracts_as/test/transfer-main-purse-to-new-purse/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/transfer_main_purse_to_new_purse.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/transfer_main_purse_to_new_purse.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/index.ts b/smart_contracts/contracts_as/test/transfer-purse-to-account-stored/assembly/index.ts similarity index 67% rename from smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/index.ts rename to smart_contracts/contracts_as/test/transfer-purse-to-account-stored/assembly/index.ts index 8a3b4adfe1..ccfa4a6b86 100644 --- a/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/index.ts +++ b/smart_contracts/contracts_as/test/transfer-purse-to-account-stored/assembly/index.ts @@ -1,16 +1,16 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {U512} from "../../../../contract-as/assembly/bignum"; -import {getMainPurse} from "../../../../contract-as/assembly/account"; -import {Key} from "../../../../contract-as/assembly/key"; -import {putKey} from "../../../../contract-as/assembly"; -import {CLValue, CLType, CLTypeTag} from "../../../../contract-as/assembly/clvalue"; -import {URef} from "../../../../contract-as/assembly/uref"; -import {toBytesMap} from "../../../../contract-as/assembly/bytesrepr"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {U512} from "../../../../contract_as/assembly/bignum"; +import {getMainPurse} from "../../../../contract_as/assembly/account"; +import {Key} from "../../../../contract_as/assembly/key"; +import {putKey} from "../../../../contract_as/assembly"; +import {CLValue, CLType, CLTypeTag} from "../../../../contract_as/assembly/clvalue"; +import {URef} from "../../../../contract_as/assembly/uref"; +import {toBytesMap} from "../../../../contract_as/assembly/bytesrepr"; import * as TransferPurseToAccount from "../../transfer-purse-to-account/assembly"; -import {getPurseBalance, transferFromPurseToAccount, TransferredTo} from "../../../../contract-as/assembly/purse"; -import {Pair} from "../../../../contract-as/assembly/pair"; +import {getPurseBalance, transferFromPurseToAccount, TransferredTo} from "../../../../contract_as/assembly/purse"; +import {Pair} from "../../../../contract_as/assembly/pair"; const ENTRY_FUNCTION_NAME = "transfer"; const PACKAGE_HASH_KEY_NAME = "transfer_purse_to_account"; diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/tsconfig.json b/smart_contracts/contracts_as/test/transfer-purse-to-account-stored/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/transfer-purse-to-account-stored/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/transfer-purse-to-account-stored/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/index.js b/smart_contracts/contracts_as/test/transfer-purse-to-account-stored/index.js similarity index 100% rename from smart-contracts/contracts-as/test/transfer-purse-to-account-stored/index.js rename to smart_contracts/contracts_as/test/transfer-purse-to-account-stored/index.js diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/package.json b/smart_contracts/contracts_as/test/transfer-purse-to-account-stored/package.json similarity index 77% rename from smart-contracts/contracts-as/test/transfer-purse-to-account-stored/package.json rename to smart_contracts/contracts_as/test/transfer-purse-to-account-stored/package.json index 93a0ef1118..74b87c27f3 100644 --- a/smart-contracts/contracts-as/test/transfer-purse-to-account-stored/package.json +++ b/smart_contracts/contracts_as/test/transfer-purse-to-account-stored/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/transfer_purse_to_account_stored.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/transfer_purse_to_account_stored.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/index.ts b/smart_contracts/contracts_as/test/transfer-purse-to-account/assembly/index.ts similarity index 77% rename from smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/index.ts rename to smart_contracts/contracts_as/test/transfer-purse-to-account/assembly/index.ts index 61d02e8889..5297322d6d 100644 --- a/smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/index.ts +++ b/smart_contracts/contracts_as/test/transfer-purse-to-account/assembly/index.ts @@ -1,13 +1,13 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error, ErrorCode} from "../../../../contract-as/assembly/error"; -import {U512} from "../../../../contract-as/assembly/bignum"; -import {getMainPurse} from "../../../../contract-as/assembly/account"; -import {Key} from "../../../../contract-as/assembly/key"; -import {putKey} from "../../../../contract-as/assembly"; -import {CLValue} from "../../../../contract-as/assembly/clvalue"; -import {getPurseBalance, transferFromPurseToAccount, TransferredTo} from "../../../../contract-as/assembly/purse"; -import {URef} from "../../../../contract-as/assembly/uref"; +import * as CL from "../../../../contract_as/assembly"; +import {Error, ErrorCode} from "../../../../contract_as/assembly/error"; +import {U512} from "../../../../contract_as/assembly/bignum"; +import {getMainPurse} from "../../../../contract_as/assembly/account"; +import {Key} from "../../../../contract_as/assembly/key"; +import {putKey} from "../../../../contract_as/assembly"; +import {CLValue} from "../../../../contract_as/assembly/clvalue"; +import {getPurseBalance, transferFromPurseToAccount, TransferredTo} from "../../../../contract_as/assembly/purse"; +import {URef} from "../../../../contract_as/assembly/uref"; const TRANSFER_RESULT_UREF_NAME = "transfer_result"; diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/tsconfig.json b/smart_contracts/contracts_as/test/transfer-purse-to-account/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/transfer-purse-to-account/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/transfer-purse-to-account/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account/index.js b/smart_contracts/contracts_as/test/transfer-purse-to-account/index.js similarity index 100% rename from smart-contracts/contracts-as/test/transfer-purse-to-account/index.js rename to smart_contracts/contracts_as/test/transfer-purse-to-account/index.js diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-account/package.json b/smart_contracts/contracts_as/test/transfer-purse-to-account/package.json similarity index 76% rename from smart-contracts/contracts-as/test/transfer-purse-to-account/package.json rename to smart_contracts/contracts_as/test/transfer-purse-to-account/package.json index 12cb453243..5230e2cef9 100644 --- a/smart-contracts/contracts-as/test/transfer-purse-to-account/package.json +++ b/smart_contracts/contracts_as/test/transfer-purse-to-account/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/transfer_purse_to_account.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/transfer_purse_to_account.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/index.ts b/smart_contracts/contracts_as/test/transfer-purse-to-purse/assembly/index.ts similarity index 87% rename from smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/index.ts rename to smart_contracts/contracts_as/test/transfer-purse-to-purse/assembly/index.ts index efbac9d0fd..438f43f849 100644 --- a/smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/index.ts +++ b/smart_contracts/contracts_as/test/transfer-purse-to-purse/assembly/index.ts @@ -1,14 +1,14 @@ //@ts-nocheck -import * as CL from "../../../../contract-as/assembly"; -import {Error} from "../../../../contract-as/assembly/error"; -import {U512} from "../../../../contract-as/assembly/bignum"; -import {getMainPurse} from "../../../../contract-as/assembly/account"; -import {Key} from "../../../../contract-as/assembly/key"; -import {getKey, hasKey, putKey} from "../../../../contract-as/assembly"; -import {CLValue} from "../../../../contract-as/assembly/clvalue"; -import {fromBytesString} from "../../../../contract-as/assembly/bytesrepr"; -import {URef} from "../../../../contract-as/assembly/uref"; -import {createPurse, getPurseBalance, transferFromPurseToPurse} from "../../../../contract-as/assembly/purse"; +import * as CL from "../../../../contract_as/assembly"; +import {Error} from "../../../../contract_as/assembly/error"; +import {U512} from "../../../../contract_as/assembly/bignum"; +import {getMainPurse} from "../../../../contract_as/assembly/account"; +import {Key} from "../../../../contract_as/assembly/key"; +import {getKey, hasKey, putKey} from "../../../../contract_as/assembly"; +import {CLValue} from "../../../../contract_as/assembly/clvalue"; +import {fromBytesString} from "../../../../contract_as/assembly/bytesrepr"; +import {URef} from "../../../../contract_as/assembly/uref"; +import {createPurse, getPurseBalance, transferFromPurseToPurse} from "../../../../contract_as/assembly/purse"; const PURSE_MAIN = "purse:main"; const PURSE_TRANSFER_RESULT = "purse_transfer_result"; diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/tsconfig.json b/smart_contracts/contracts_as/test/transfer-purse-to-purse/assembly/tsconfig.json similarity index 100% rename from smart-contracts/contracts-as/test/transfer-purse-to-purse/assembly/tsconfig.json rename to smart_contracts/contracts_as/test/transfer-purse-to-purse/assembly/tsconfig.json diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-purse/index.js b/smart_contracts/contracts_as/test/transfer-purse-to-purse/index.js similarity index 100% rename from smart-contracts/contracts-as/test/transfer-purse-to-purse/index.js rename to smart_contracts/contracts_as/test/transfer-purse-to-purse/index.js diff --git a/smart-contracts/contracts-as/test/transfer-purse-to-purse/package.json b/smart_contracts/contracts_as/test/transfer-purse-to-purse/package.json similarity index 76% rename from smart-contracts/contracts-as/test/transfer-purse-to-purse/package.json rename to smart_contracts/contracts_as/test/transfer-purse-to-purse/package.json index 013fd83358..147d4ae642 100644 --- a/smart-contracts/contracts-as/test/transfer-purse-to-purse/package.json +++ b/smart_contracts/contracts_as/test/transfer-purse-to-purse/package.json @@ -1,6 +1,6 @@ { "scripts": { - "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target-as/transfer_purse_to_purse.wasm --optimize --use abort=", + "asbuild:optimized": "asc --lib ../../.. assembly/index.ts -b ../../../../target_as/transfer_purse_to_purse.wasm --optimize --use abort=", "asbuild": "npm run asbuild:optimized" }, "devDependencies": { From b4b4067c5fd864a058eb3db3d3892d0256b5eef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Papierski?= Date: Tue, 7 Jul 2020 12:33:57 +0200 Subject: [PATCH 8/8] NDRS-90: Fix `make check` and compiler issues. --- Cargo.toml | 4 +++- Makefile | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 33396e6250..17468b8fed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,9 @@ default-members = [ debug = true [profile.release] -lto = true +# TODO: nightly compiler has issues with linking libraries with LTO enabled. +# Change this back to true once stable is supported by default. +lto = false [profile.bench] lto = true diff --git a/Makefile b/Makefile index d07d833f3f..adc2abaf90 100644 --- a/Makefile +++ b/Makefile @@ -134,7 +134,7 @@ test-rs: .PHONY: test-as test-as: setup-as - cd contract_as && npm run asbuild && npm run test + cd smart_contracts/contract_as && npm run asbuild && npm run test .PHONY: test test: test-rs test-as @@ -264,8 +264,8 @@ setup-nightly-rs: RUST_TOOLCHAIN := nightly setup-nightly-rs: setup-rs .PHONY: setup-as -setup-as: contract_as/package.json - cd contract_as && $(NPM) ci +setup-as: smart_contracts/contract_as/package.json + cd smart_contracts/contract_as && $(NPM) ci .PHONY: setup setup: setup-rs setup-as