본문으로 건너뛰기

온체인 랜덤니스

Move 함수는 RandomGenerator의 새 instance를 만들고 이를 사용해 서로 다른 type의 random value를 생성할 수 있다. 예를 들어 generate_u128(&mut generator), generate_u8_in_range(&mut generator, 1, 6) 또는 다음 예시와 같다:

entry fun roll_dice(r: &Random, ctx: &mut TxContext): Dice {
let mut generator = new_generator(r, ctx); // generator is a PRG
Dice { value: random::generate_u8_in_range(&mut generator, 1, 6) }
}

Random은 예약된 address 0x8을 가진다. Sui에서 randomness에 접근하는 Move API는 random.move를 참조한다.

정보

Random은 shared object이지만 mutable operation에는 접근할 수 없다. 이를 수정하려는 transaction은 실패한다.

Limited resources and Random dependent flows​

transaction이 사용할 수 있는 일부 resource에는 제한이 있다는 점에 유의한다. 주의하지 않으면 공격자가 함수의 resource가 고갈되는 지점을 의도적으로 제어하여 애플리케이션을 망가뜨리거나 악용할 수 있다. 구체적으로 gas가 그러한 resource이다.

Random API는 이런 종류의 공격을 자동으로 막아주지 않으므로 contract를 설계할 때 이 미묘한 점을 인지해야 한다.

transaction당 고려해야 할 다른 제한 resource는 다음과 같다:

  • 새 object의 수.

  • dynamic field를 포함해 사용할 수 있는 object의 수.

  • 발생시키는 event의 수.

  • 생성, 삭제, 전송되는 UID의 수.

Click to open

ProtocolConfig limit의 전체 목록.

/// Constants that change the behavior of the protocol.
///
/// The value of each constant here must be fixed for a given protocol version. To change the value
/// of a constant, advance the protocol version, and add support for it in `get_for_version` under
/// the new version number.
/// (below).
///
/// To add a new field to this struct, use the following procedure:
/// - Advance the protocol version.
/// - Add the field as a private `Option<T>` to the struct.
/// - Initialize the field to `None` in prior protocol versions.
/// - Initialize the field to `Some(val)` for your new protocol version.
/// - Add a public getter that simply unwraps the field.
/// - Two public getters of the form `field(&self) -> field_type`
/// and `field_as_option(&self) -> Option<field_type>` will be automatically generated for you.
/// Example for a field: `new_constant: Option<u64>`
/// ```rust,ignore
/// pub fn new_constant(&self) -> u64 {
/// self.new_constant.expect(Self::CONSTANT_ERR_MSG)
/// }
/// pub fn new_constant_as_option(&self) -> Option<u64> {
/// self.new_constant.expect(Self::CONSTANT_ERR_MSG)
/// }
/// ```
/// With `pub fn new_constant(&self) -> u64`, if the constant is accessed in a protocol version
/// in which it is not defined, the validator will crash. (Crashing is necessary because
/// this type of error would almost always result in forking if not prevented here).
/// If you don't want the validator to crash, you can use the
/// `pub fn new_constant_as_option(&self) -> Option<u64>` getter, which will
/// return `None` if the field is not defined at that version.
/// - If you want a customized getter, you can add a method in the impl.
#[skip_serializing_none]
#[derive(Clone, Serialize, Debug, ProtocolConfigAccessors, ProtocolConfigOverride)]
pub struct ProtocolConfig {
pub version: ProtocolVersion,

feature_flags: FeatureFlags,

// ==== Transaction input limits ====
/// Maximum serialized size of a transaction (in bytes).
max_tx_size_bytes: Option<u64>,

/// Maximum number of input objects to a transaction. Enforced by the transaction input checker
max_input_objects: Option<u64>,

/// Max size of objects a transaction can write to disk after completion. Enforce by the Sui adapter.
/// This is the sum of the serialized size of all objects written to disk.
/// The max size of individual objects on the other hand is `max_move_object_size`.
max_size_written_objects: Option<u64>,
/// Max size of objects a system transaction can write to disk after completion. Enforce by the Sui adapter.
/// Similar to `max_size_written_objects` but for system transactions.
max_size_written_objects_system_tx: Option<u64>,

/// Maximum size of serialized transaction effects.
max_serialized_tx_effects_size_bytes: Option<u64>,

/// Maximum size of serialized transaction effects for system transactions.
max_serialized_tx_effects_size_bytes_system_tx: Option<u64>,

/// Maximum number of gas payment objects for a transaction.
max_gas_payment_objects: Option<u32>,

/// Maximum number of modules in a Publish transaction.
max_modules_in_publish: Option<u32>,

/// Maximum number of transitive dependencies in a package when publishing.
max_package_dependencies: Option<u32>,

/// Maximum number of arguments in a move call or a ProgrammableTransaction's
/// TransferObjects command.
max_arguments: Option<u32>,

/// Maximum number of total type arguments, computed recursively.
max_type_arguments: Option<u32>,

/// Maximum depth of an individual type argument.
max_type_argument_depth: Option<u32>,

/// Maximum size of a Pure CallArg.
max_pure_argument_size: Option<u32>,

/// Maximum number of Commands in a ProgrammableTransaction.
max_programmable_tx_commands: Option<u32>,

// ==== Move VM, Move bytecode verifier, and execution limits ===
/// Maximum Move bytecode version the VM understands. All older versions are accepted.
move_binary_format_version: Option<u32>,
min_move_binary_format_version: Option<u32>,

/// Configuration controlling binary tables size.
binary_module_handles: Option<u16>,
binary_struct_handles: Option<u16>,
binary_function_handles: Option<u16>,
binary_function_instantiations: Option<u16>,
binary_signatures: Option<u16>,
binary_constant_pool: Option<u16>,
binary_identifiers: Option<u16>,
binary_address_identifiers: Option<u16>,
binary_struct_defs: Option<u16>,
binary_struct_def_instantiations: Option<u16>,
binary_function_defs: Option<u16>,
binary_field_handles: Option<u16>,
binary_field_instantiations: Option<u16>,
binary_friend_decls: Option<u16>,
binary_enum_defs: Option<u16>,
binary_enum_def_instantiations: Option<u16>,
binary_variant_handles: Option<u16>,
binary_variant_instantiation_handles: Option<u16>,

/// Maximum size of the `contents` part of an object, in bytes. Enforced by the Sui adapter when effects are produced.
max_move_object_size: Option<u64>,

// TODO: Option<increase to 500 KB. currently, publishing a package > 500 KB exceeds the max computation gas cost
/// Maximum size of a Move package object, in bytes. Enforced by the Sui adapter at the end of a publish transaction.
max_move_package_size: Option<u64>,

/// Max number of publish or upgrade commands allowed in a programmable transaction block.
max_publish_or_upgrade_per_ptb: Option<u64>,

/// Maximum gas budget in MIST that a transaction can use.
max_tx_gas: Option<u64>,

/// Maximum amount of the proposed gas price in MIST (defined in the transaction).
max_gas_price: Option<u64>,

/// For aborted txns, we cap the gas price at a factor of RGP. This lowers risk of setting higher priority gas price
/// if there's a chance the txn will abort.
max_gas_price_rgp_factor_for_aborted_transactions: Option<u64>,

/// The max computation bucket for gas. This is the max that can be charged for computation.
max_gas_computation_bucket: Option<u64>,

// Define the value used to round up computation gas charges
gas_rounding_step: Option<u64>,

/// Maximum number of nested loops. Enforced by the Move bytecode verifier.
max_loop_depth: Option<u64>,

/// Maximum number of type arguments that can be bound to generic type parameters. Enforced by the Move bytecode verifier.
max_generic_instantiation_length: Option<u64>,

/// Maximum number of parameters that a Move function can have. Enforced by the Move bytecode verifier.
max_function_parameters: Option<u64>,

/// Maximum number of basic blocks that a Move function can have. Enforced by the Move bytecode verifier.
max_basic_blocks: Option<u64>,

/// Maximum stack size value. Enforced by the Move bytecode verifier.
max_value_stack_size: Option<u64>,

/// Maximum number of "type nodes", a metric for how big a SignatureToken will be when expanded into a fully qualified type. Enforced by the Move bytecode verifier.
max_type_nodes: Option<u64>,

/// Maximum number of push instructions in one function. Enforced by the Move bytecode verifier.
max_push_size: Option<u64>,

/// Maximum number of struct definitions in a module. Enforced by the Move bytecode verifier.
max_struct_definitions: Option<u64>,

/// Maximum number of function definitions in a module. Enforced by the Move bytecode verifier.
max_function_definitions: Option<u64>,

/// Maximum number of fields allowed in a struct definition. Enforced by the Move bytecode verifier.
max_fields_in_struct: Option<u64>,

/// Maximum dependency depth. Enforced by the Move linker when loading dependent modules.
max_dependency_depth: Option<u64>,

/// Maximum number of Move events that a single transaction can emit. Enforced by the VM during execution.
max_num_event_emit: Option<u64>,

/// Maximum number of new IDs that a single transaction can create. Enforced by the VM during execution.
max_num_new_move_object_ids: Option<u64>,

/// Maximum number of new IDs that a single system transaction can create. Enforced by the VM during execution.
max_num_new_move_object_ids_system_tx: Option<u64>,

/// Maximum number of IDs that a single transaction can delete. Enforced by the VM during execution.
max_num_deleted_move_object_ids: Option<u64>,

/// Maximum number of IDs that a single system transaction can delete. Enforced by the VM during execution.
max_num_deleted_move_object_ids_system_tx: Option<u64>,

/// Maximum number of IDs that a single transaction can transfer. Enforced by the VM during execution.
max_num_transferred_move_object_ids: Option<u64>,

/// Maximum number of IDs that a single system transaction can transfer. Enforced by the VM during execution.
max_num_transferred_move_object_ids_system_tx: Option<u64>,

/// Maximum size of a Move user event. Enforced by the VM during execution.
max_event_emit_size: Option<u64>,

/// Maximum size of a Move user event. Enforced by the VM during execution.
max_event_emit_size_total: Option<u64>,

/// Maximum length of a vector in Move. Enforced by the VM during execution, and for constants, by the verifier.
max_move_vector_len: Option<u64>,

/// Maximum length of an `Identifier` in Move. Enforced by the bytecode verifier at signing.
max_move_identifier_len: Option<u64>,

/// Maximum depth of a Move value within the VM.
max_move_value_depth: Option<u64>,

/// Maximum number of variants in an enum. Enforced by the bytecode verifier at signing.
max_move_enum_variants: Option<u64>,

/// Maximum number of back edges in Move function. Enforced by the bytecode verifier at signing.
max_back_edges_per_function: Option<u64>,

/// Maximum number of back edges in Move module. Enforced by the bytecode verifier at signing.
max_back_edges_per_module: Option<u64>,

/// Maximum number of meter `ticks` spent verifying a Move function. Enforced by the bytecode verifier at signing.
max_verifier_meter_ticks_per_function: Option<u64>,

/// Maximum number of meter `ticks` spent verifying a Move module. Enforced by the bytecode verifier at signing.
max_meter_ticks_per_module: Option<u64>,

/// Maximum number of meter `ticks` spent verifying a Move package. Enforced by the bytecode verifier at signing.
max_meter_ticks_per_package: Option<u64>,

// === Object runtime internal operation limits ====
// These affect dynamic fields
/// Maximum number of cached objects in the object runtime ObjectStore. Enforced by object runtime during execution
object_runtime_max_num_cached_objects: Option<u64>,

/// Maximum number of cached objects in the object runtime ObjectStore in system transaction. Enforced by object runtime during execution
object_runtime_max_num_cached_objects_system_tx: Option<u64>,

/// Maximum number of stored objects accessed by object runtime ObjectStore. Enforced by object runtime during execution
object_runtime_max_num_store_entries: Option<u64>,

/// Maximum number of stored objects accessed by object runtime ObjectStore in system transaction. Enforced by object runtime during execution
object_runtime_max_num_store_entries_system_tx: Option<u64>,

// === Execution gas costs ====
/// Base cost for any Sui transaction
base_tx_cost_fixed: Option<u64>,

/// Additional cost for a transaction that publishes a package
/// i.e., the base cost of such a transaction is base_tx_cost_fixed + package_publish_cost_fixed
package_publish_cost_fixed: Option<u64>,

/// Cost per byte of a Move call transaction
/// i.e., the cost of such a transaction is base_cost + (base_tx_cost_per_byte * size)
base_tx_cost_per_byte: Option<u64>,

/// Cost per byte for a transaction that publishes a package
package_publish_cost_per_byte: Option<u64>,

// Per-byte cost of reading an object during transaction execution
obj_access_cost_read_per_byte: Option<u64>,

// Per-byte cost of writing an object during transaction execution
obj_access_cost_mutate_per_byte: Option<u64>,

// Per-byte cost of deleting an object during transaction execution
obj_access_cost_delete_per_byte: Option<u64>,

/// Per-byte cost charged for each input object to a transaction.
/// Meant to approximate the cost of checking locks for each object
// TODO: Option<I'm not sure that this cost makes sense. Checking locks is "free"
// in the sense that an invalid tx that can never be committed/pay gas can
// force validators to check an arbitrary number of locks. If those checks are
// "free" for invalid transactions, why charge for them in valid transactions
// TODO: Option<if we keep this, I think we probably want it to be a fixed cost rather
// than a per-byte cost. checking an object lock should not require loading an
// entire object, just consulting an ID -> tx digest map
obj_access_cost_verify_per_byte: Option<u64>,

// Maximal nodes which are allowed when converting to a type layout.
max_type_to_layout_nodes: Option<u64>,

// Maximal size in bytes that a PTB value can be
max_ptb_value_size: Option<u64>,

// === Gas version. gas model ===
/// Gas model version, what code we are using to charge gas
gas_model_version: Option<u64>,

// === Storage gas costs ===
/// Per-byte cost of storing an object in the Sui global object store. Some of this cost may be refundable if the object is later freed
obj_data_cost_refundable: Option<u64>,

// Per-byte cost of storing an object in the Sui transaction log (e.g., in CertifiedTransactionEffects)
// This depends on the size of various fields including the effects
// TODO: Option<I don't fully understand this^ and more details would be useful
obj_metadata_cost_non_refundable: Option<u64>,

// === Tokenomics ===

// TODO: Option<this should be changed to u64.
/// Sender of a txn that touches an object will get this percent of the storage rebate back.
/// In basis point.
storage_rebate_rate: Option<u64>,

/// 5% of the storage fund's share of rewards are reinvested into the storage fund.
/// In basis point.
storage_fund_reinvest_rate: Option<u64>,

/// The share of rewards that will be slashed and redistributed is 50%.
/// In basis point.
reward_slashing_rate: Option<u64>,

/// Unit gas price, Mist per internal gas unit.
storage_gas_price: Option<u64>,

/// Per-object storage cost for accumulator objects, used during end-of-epoch accounting.
accumulator_object_storage_cost: Option<u64>,

// === Core Protocol ===
/// Max number of transactions per checkpoint.
/// Note that this is a protocol constant and not a config as validators must have this set to
/// the same value, otherwise they *will* fork.
max_transactions_per_checkpoint: Option<u64>,

/// Max size of a checkpoint in bytes.
/// Note that this is a protocol constant and not a config as validators must have this set to
/// the same value, otherwise they *will* fork.
max_checkpoint_size_bytes: Option<u64>,

/// A protocol upgrade always requires 2f+1 stake to agree. We support a buffer of additional
/// stake (as a fraction of f, expressed in basis points) that is required before an upgrade
/// can happen automatically. 10000bps would indicate that complete unanimity is required (all
/// 3f+1 must vote), while 0bps would indicate that 2f+1 is sufficient.
buffer_stake_for_protocol_upgrade_bps: Option<u64>,

// === Native Function Costs ===

// `address` module
// Cost params for the Move native function `address::from_bytes(bytes: vector<u8>)`
address_from_bytes_cost_base: Option<u64>,
// Cost params for the Move native function `address::to_u256(address): u256`
address_to_u256_cost_base: Option<u64>,
// Cost params for the Move native function `address::from_u256(u256): address`
address_from_u256_cost_base: Option<u64>,

// `config` module
// Cost params for the Move native function `read_setting_impl<Name: copy + drop + store,
// SettingValue: key + store, SettingDataValue: store, Value: copy + drop + store,
// >(config: address, name: address, current_epoch: u64): Option<Value>`
config_read_setting_impl_cost_base: Option<u64>,
config_read_setting_impl_cost_per_byte: Option<u64>,

// `dynamic_field` module
// Cost params for the Move native function `hash_type_and_key<K: copy + drop + store>(parent: address, k: K): address`
dynamic_field_hash_type_and_key_cost_base: Option<u64>,
dynamic_field_hash_type_and_key_type_cost_per_byte: Option<u64>,
dynamic_field_hash_type_and_key_value_cost_per_byte: Option<u64>,
dynamic_field_hash_type_and_key_type_tag_cost_per_byte: Option<u64>,
// Cost params for the Move native function `add_child_object<Child: key>(parent: address, child: Child)`
dynamic_field_add_child_object_cost_base: Option<u64>,
dynamic_field_add_child_object_type_cost_per_byte: Option<u64>,
dynamic_field_add_child_object_value_cost_per_byte: Option<u64>,
dynamic_field_add_child_object_struct_tag_cost_per_byte: Option<u64>,
// Cost params for the Move native function `borrow_child_object_mut<Child: key>(parent: &mut UID, id: address): &mut Child`
dynamic_field_borrow_child_object_cost_base: Option<u64>,
dynamic_field_borrow_child_object_child_ref_cost_per_byte: Option<u64>,
dynamic_field_borrow_child_object_type_cost_per_byte: Option<u64>,
// Cost params for the Move native function `remove_child_object<Child: key>(parent: address, id: address): Child`
dynamic_field_remove_child_object_cost_base: Option<u64>,
dynamic_field_remove_child_object_child_cost_per_byte: Option<u64>,
dynamic_field_remove_child_object_type_cost_per_byte: Option<u64>,
// Cost params for the Move native function `has_child_object(parent: address, id: address): bool`
dynamic_field_has_child_object_cost_base: Option<u64>,
// Cost params for the Move native function `has_child_object_with_ty<Child: key>(parent: address, id: address): bool`
dynamic_field_has_child_object_with_ty_cost_base: Option<u64>,
dynamic_field_has_child_object_with_ty_type_cost_per_byte: Option<u64>,
dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: Option<u64>,

// `event` module
// Cost params for the Move native function `event::emit<T: copy + drop>(event: T)`
event_emit_cost_base: Option<u64>,
event_emit_value_size_derivation_cost_per_byte: Option<u64>,
event_emit_tag_size_derivation_cost_per_byte: Option<u64>,
event_emit_output_cost_per_byte: Option<u64>,
event_emit_auth_stream_cost: Option<u64>,

// `object` module
// Cost params for the Move native function `borrow_uid<T: key>(obj: &T): &UID`
object_borrow_uid_cost_base: Option<u64>,
// Cost params for the Move native function `delete_impl(id: address)`
object_delete_impl_cost_base: Option<u64>,
// Cost params for the Move native function `record_new_uid(id: address)`
object_record_new_uid_cost_base: Option<u64>,

// Transfer
// Cost params for the Move native function `transfer_impl<T: key>(obj: T, recipient: address)`
transfer_transfer_internal_cost_base: Option<u64>,
// Cost params for the Move native function `party_transfer_impl<T: key>(obj: T, party_members: vector<address>)`
transfer_party_transfer_internal_cost_base: Option<u64>,
// Cost params for the Move native function `freeze_object<T: key>(obj: T)`
transfer_freeze_object_cost_base: Option<u64>,
// Cost params for the Move native function `share_object<T: key>(obj: T)`
transfer_share_object_cost_base: Option<u64>,
// Cost params for the Move native function
// `receive_object<T: key>(p: &mut UID, recv: Receiving<T>T)`
transfer_receive_object_cost_base: Option<u64>,
transfer_receive_object_cost_per_byte: Option<u64>,
transfer_receive_object_type_cost_per_byte: Option<u64>,

// TxContext
// Cost params for the Move native function `transfer_impl<T: key>(obj: T, recipient: address)`
tx_context_derive_id_cost_base: Option<u64>,
tx_context_fresh_id_cost_base: Option<u64>,
tx_context_sender_cost_base: Option<u64>,
tx_context_epoch_cost_base: Option<u64>,
tx_context_epoch_timestamp_ms_cost_base: Option<u64>,
tx_context_sponsor_cost_base: Option<u64>,
tx_context_rgp_cost_base: Option<u64>,
tx_context_gas_price_cost_base: Option<u64>,
tx_context_gas_budget_cost_base: Option<u64>,
tx_context_ids_created_cost_base: Option<u64>,
tx_context_replace_cost_base: Option<u64>,

// Types
// Cost params for the Move native function `is_one_time_witness<T: drop>(_: &T): bool`
types_is_one_time_witness_cost_base: Option<u64>,
types_is_one_time_witness_type_tag_cost_per_byte: Option<u64>,
types_is_one_time_witness_type_cost_per_byte: Option<u64>,

// Validator
// Cost params for the Move native function `validate_metadata_bcs(metadata: vector<u8>)`
validator_validate_metadata_cost_base: Option<u64>,
validator_validate_metadata_data_cost_per_byte: Option<u64>,

// Crypto natives
crypto_invalid_arguments_cost: Option<u64>,
// bls12381::bls12381_min_sig_verify
bls12381_bls12381_min_sig_verify_cost_base: Option<u64>,
bls12381_bls12381_min_sig_verify_msg_cost_per_byte: Option<u64>,
bls12381_bls12381_min_sig_verify_msg_cost_per_block: Option<u64>,

// bls12381::bls12381_min_pk_verify
bls12381_bls12381_min_pk_verify_cost_base: Option<u64>,
bls12381_bls12381_min_pk_verify_msg_cost_per_byte: Option<u64>,
bls12381_bls12381_min_pk_verify_msg_cost_per_block: Option<u64>,

// ecdsa_k1::ecrecover
ecdsa_k1_ecrecover_keccak256_cost_base: Option<u64>,
ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: Option<u64>,
ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: Option<u64>,
ecdsa_k1_ecrecover_sha256_cost_base: Option<u64>,
ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: Option<u64>,
ecdsa_k1_ecrecover_sha256_msg_cost_per_block: Option<u64>,

// ecdsa_k1::decompress_pubkey
ecdsa_k1_decompress_pubkey_cost_base: Option<u64>,

// ecdsa_k1::secp256k1_verify
ecdsa_k1_secp256k1_verify_keccak256_cost_base: Option<u64>,
ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: Option<u64>,
ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: Option<u64>,
ecdsa_k1_secp256k1_verify_sha256_cost_base: Option<u64>,
ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: Option<u64>,
ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: Option<u64>,

// ecdsa_r1::ecrecover
ecdsa_r1_ecrecover_keccak256_cost_base: Option<u64>,
ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: Option<u64>,
ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: Option<u64>,
ecdsa_r1_ecrecover_sha256_cost_base: Option<u64>,
ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: Option<u64>,
ecdsa_r1_ecrecover_sha256_msg_cost_per_block: Option<u64>,

// ecdsa_r1::secp256k1_verify
ecdsa_r1_secp256r1_verify_keccak256_cost_base: Option<u64>,
ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: Option<u64>,
ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: Option<u64>,
ecdsa_r1_secp256r1_verify_sha256_cost_base: Option<u64>,
ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: Option<u64>,
ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: Option<u64>,

// ecvrf::verify
ecvrf_ecvrf_verify_cost_base: Option<u64>,
ecvrf_ecvrf_verify_alpha_string_cost_per_byte: Option<u64>,
ecvrf_ecvrf_verify_alpha_string_cost_per_block: Option<u64>,

// ed25519
ed25519_ed25519_verify_cost_base: Option<u64>,
ed25519_ed25519_verify_msg_cost_per_byte: Option<u64>,
ed25519_ed25519_verify_msg_cost_per_block: Option<u64>,

// groth16::prepare_verifying_key
groth16_prepare_verifying_key_bls12381_cost_base: Option<u64>,
groth16_prepare_verifying_key_bn254_cost_base: Option<u64>,

// groth16::verify_groth16_proof_internal
groth16_verify_groth16_proof_internal_bls12381_cost_base: Option<u64>,
groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: Option<u64>,
groth16_verify_groth16_proof_internal_bn254_cost_base: Option<u64>,
groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: Option<u64>,
groth16_verify_groth16_proof_internal_public_input_cost_per_byte: Option<u64>,

// hash::blake2b256
hash_blake2b256_cost_base: Option<u64>,
hash_blake2b256_data_cost_per_byte: Option<u64>,
hash_blake2b256_data_cost_per_block: Option<u64>,

// hash::keccak256
hash_keccak256_cost_base: Option<u64>,
hash_keccak256_data_cost_per_byte: Option<u64>,
hash_keccak256_data_cost_per_block: Option<u64>,

// poseidon::poseidon_bn254
poseidon_bn254_cost_base: Option<u64>,
poseidon_bn254_cost_per_block: Option<u64>,

// group_ops
group_ops_bls12381_decode_scalar_cost: Option<u64>,
group_ops_bls12381_decode_g1_cost: Option<u64>,
group_ops_bls12381_decode_g2_cost: Option<u64>,
group_ops_bls12381_decode_gt_cost: Option<u64>,
group_ops_bls12381_scalar_add_cost: Option<u64>,
group_ops_bls12381_g1_add_cost: Option<u64>,
group_ops_bls12381_g2_add_cost: Option<u64>,
group_ops_bls12381_gt_add_cost: Option<u64>,
group_ops_bls12381_scalar_sub_cost: Option<u64>,
group_ops_bls12381_g1_sub_cost: Option<u64>,
group_ops_bls12381_g2_sub_cost: Option<u64>,
group_ops_bls12381_gt_sub_cost: Option<u64>,
group_ops_bls12381_scalar_mul_cost: Option<u64>,
group_ops_bls12381_g1_mul_cost: Option<u64>,
group_ops_bls12381_g2_mul_cost: Option<u64>,
group_ops_bls12381_gt_mul_cost: Option<u64>,
group_ops_bls12381_scalar_div_cost: Option<u64>,
group_ops_bls12381_g1_div_cost: Option<u64>,
group_ops_bls12381_g2_div_cost: Option<u64>,
group_ops_bls12381_gt_div_cost: Option<u64>,
group_ops_bls12381_g1_hash_to_base_cost: Option<u64>,
group_ops_bls12381_g2_hash_to_base_cost: Option<u64>,
group_ops_bls12381_g1_hash_to_cost_per_byte: Option<u64>,
group_ops_bls12381_g2_hash_to_cost_per_byte: Option<u64>,
group_ops_bls12381_g1_msm_base_cost: Option<u64>,
group_ops_bls12381_g2_msm_base_cost: Option<u64>,
group_ops_bls12381_g1_msm_base_cost_per_input: Option<u64>,
group_ops_bls12381_g2_msm_base_cost_per_input: Option<u64>,
group_ops_bls12381_msm_max_len: Option<u32>,
group_ops_bls12381_pairing_cost: Option<u64>,
group_ops_bls12381_g1_to_uncompressed_g1_cost: Option<u64>,
group_ops_bls12381_uncompressed_g1_to_g1_cost: Option<u64>,
group_ops_bls12381_uncompressed_g1_sum_base_cost: Option<u64>,
group_ops_bls12381_uncompressed_g1_sum_cost_per_term: Option<u64>,
group_ops_bls12381_uncompressed_g1_sum_max_terms: Option<u64>,

group_ops_ristretto_decode_scalar_cost: Option<u64>,
group_ops_ristretto_decode_point_cost: Option<u64>,
group_ops_ristretto_scalar_add_cost: Option<u64>,
group_ops_ristretto_point_add_cost: Option<u64>,
group_ops_ristretto_scalar_sub_cost: Option<u64>,
group_ops_ristretto_point_sub_cost: Option<u64>,
group_ops_ristretto_scalar_mul_cost: Option<u64>,
group_ops_ristretto_point_mul_cost: Option<u64>,
group_ops_ristretto_scalar_div_cost: Option<u64>,
group_ops_ristretto_point_div_cost: Option<u64>,

verify_bulletproofs_ristretto255_base_cost: Option<u64>,
verify_bulletproofs_ristretto255_cost_per_bit_and_commitment: Option<u64>,

// hmac::hmac_sha3_256
hmac_hmac_sha3_256_cost_base: Option<u64>,
hmac_hmac_sha3_256_input_cost_per_byte: Option<u64>,
hmac_hmac_sha3_256_input_cost_per_block: Option<u64>,

// zklogin::check_zklogin_id
check_zklogin_id_cost_base: Option<u64>,
// zklogin::check_zklogin_issuer
check_zklogin_issuer_cost_base: Option<u64>,

vdf_verify_vdf_cost: Option<u64>,
vdf_hash_to_input_cost: Option<u64>,

// nitro_attestation::load_nitro_attestation
nitro_attestation_parse_base_cost: Option<u64>,
nitro_attestation_parse_cost_per_byte: Option<u64>,
nitro_attestation_verify_base_cost: Option<u64>,
nitro_attestation_verify_cost_per_cert: Option<u64>,

// Stdlib costs
bcs_per_byte_serialized_cost: Option<u64>,
bcs_legacy_min_output_size_cost: Option<u64>,
bcs_failure_cost: Option<u64>,

hash_sha2_256_base_cost: Option<u64>,
hash_sha2_256_per_byte_cost: Option<u64>,
hash_sha2_256_legacy_min_input_len_cost: Option<u64>,
hash_sha3_256_base_cost: Option<u64>,
hash_sha3_256_per_byte_cost: Option<u64>,
hash_sha3_256_legacy_min_input_len_cost: Option<u64>,
type_name_get_base_cost: Option<u64>,
type_name_get_per_byte_cost: Option<u64>,
type_name_id_base_cost: Option<u64>,

string_check_utf8_base_cost: Option<u64>,
string_check_utf8_per_byte_cost: Option<u64>,
string_is_char_boundary_base_cost: Option<u64>,
string_sub_string_base_cost: Option<u64>,
string_sub_string_per_byte_cost: Option<u64>,
string_index_of_base_cost: Option<u64>,
string_index_of_per_byte_pattern_cost: Option<u64>,
string_index_of_per_byte_searched_cost: Option<u64>,

vector_empty_base_cost: Option<u64>,
vector_length_base_cost: Option<u64>,
vector_push_back_base_cost: Option<u64>,
vector_push_back_legacy_per_abstract_memory_unit_cost: Option<u64>,
vector_borrow_base_cost: Option<u64>,
vector_pop_back_base_cost: Option<u64>,
vector_destroy_empty_base_cost: Option<u64>,
vector_swap_base_cost: Option<u64>,
debug_print_base_cost: Option<u64>,
debug_print_stack_trace_base_cost: Option<u64>,

// ==== Ephemeral (consensus only) params deleted ====
//
// Const params for consensus scoring decision
// The scaling factor property for the MED outlier detection
// scoring_decision_mad_divisor: Option<f64>,
// The cutoff value for the MED outlier detection
// scoring_decision_cutoff_value: Option<f64>,
/// === Execution Version ===
execution_version: Option<u64>,

// Dictates the threshold (percentage of stake) that is used to calculate the "bad" nodes to be
// swapped when creating the consensus schedule. The values should be of the range [0 - 33]. Anything
// above 33 (f) will not be allowed.
consensus_bad_nodes_stake_threshold: Option<u64>,

max_jwk_votes_per_validator_per_epoch: Option<u64>,
// The maximum age of a JWK in epochs before it is removed from the AuthenticatorState object.
// Applied at the end of an epoch as a delta from the new epoch value, so setting this to 1
// will cause the new epoch to start with JWKs from the previous epoch still valid.
max_age_of_jwk_in_epochs: Option<u64>,

// === random beacon ===
/// Maximum allowed precision loss when reducing voting weights for the random beacon
/// protocol.
random_beacon_reduction_allowed_delta: Option<u16>,

/// Minimum number of shares below which voting weights will not be reduced for the
/// random beacon protocol.
random_beacon_reduction_lower_bound: Option<u32>,

/// Consensus Round after which DKG should be aborted and randomness disabled for
/// the epoch, if it hasn't already completed.
random_beacon_dkg_timeout_round: Option<u32>,

/// Minimum interval between consecutive rounds of generated randomness.
random_beacon_min_round_interval_ms: Option<u64>,

/// Version of the random beacon DKG protocol.
/// 0 was deprecated (and currently not supported), 1 is the default version.
random_beacon_dkg_version: Option<u64>,

/// The maximum serialised transaction size (in bytes) accepted by consensus. That should be bigger than the
/// `max_tx_size_bytes` with some additional headroom.
consensus_max_transaction_size_bytes: Option<u64>,
/// The maximum size of transactions included in a consensus block.
consensus_max_transactions_in_block_bytes: Option<u64>,
/// The maximum number of transactions included in a consensus block.
consensus_max_num_transactions_in_block: Option<u64>,

/// The maximum number of rounds where transaction voting is allowed.
consensus_voting_rounds: Option<u32>,

/// DEPRECATED. Do not use.
max_accumulated_txn_cost_per_object_in_narwhal_commit: Option<u64>,

/// The max number of consensus rounds a transaction can be deferred due to shared object congestion.
/// Transactions will be cancelled after this many rounds.
max_deferral_rounds_for_congestion_control: Option<u64>,

/// DEPRECATED. Do not use.
max_txn_cost_overage_per_object_in_commit: Option<u64>,

/// DEPRECATED. Do not use.
allowed_txn_cost_overage_burst_per_object_in_commit: Option<u64>,

/// Minimum interval of commit timestamps between consecutive checkpoints.
min_checkpoint_interval_ms: Option<u64>,

/// Version number to use for version_specific_data in `CheckpointSummary`.
checkpoint_summary_version_specific_data: Option<u64>,

/// The max number of transactions that can be included in a single Soft Bundle.
max_soft_bundle_size: Option<u64>,

/// Whether to try to form bridge committee
// Note: this is not a feature flag because we want to distinguish between
// `None` and `Some(false)`, as committee was already finalized on Testnet.
bridge_should_try_to_finalize_committee: Option<bool>,

/// The max accumulated txn execution cost per object in a mysticeti. Transactions
/// in a commit will be deferred once their touch shared objects hit this limit,
/// unless the selected congestion control mode allows overage.
/// This config plays the same role as `max_accumulated_txn_cost_per_object_in_narwhal_commit`
/// but for mysticeti commits due to that mysticeti has higher commit rate.
max_accumulated_txn_cost_per_object_in_mysticeti_commit: Option<u64>,

/// As above, but separate per-commit budget for transactions that use randomness.
/// If not configured, uses the setting for `max_accumulated_txn_cost_per_object_in_mysticeti_commit`.
max_accumulated_randomness_txn_cost_per_object_in_mysticeti_commit: Option<u64>,

/// Configures the garbage collection depth for consensus. When is unset or `0` then the garbage collection
/// is disabled.
consensus_gc_depth: Option<u32>,

/// DEPRECATED. Do not use.
gas_budget_based_txn_cost_cap_factor: Option<u64>,

/// DEPRECATED. Do not use.
gas_budget_based_txn_cost_absolute_cap_commit_count: Option<u64>,

/// SIP-45: K in the formula `amplification_factor = max(0, gas_price / reference_gas_price - K)`.
/// This is the threshold for activating consensus amplification.
sip_45_consensus_amplification_threshold: Option<u64>,

/// DEPRECATED: this was an ephemeral feature flag only used in per-epoch tables, which has now
/// been deployed everywhere.
use_object_per_epoch_marker_table_v2: Option<bool>,

/// The number of commits to consider when computing a deterministic commit rate.
consensus_commit_rate_estimation_window_size: Option<u32>,

/// A list of effective AliasedAddress.
/// For each pair, `aliased` is allowed to act as `original` for any of the transaction digests
/// listed in `tx_digests`
#[serde(skip_serializing_if = "Vec::is_empty")]
aliased_addresses: Vec<AliasedAddress>,

/// The base charge for each command in a programmable transaction. This is a fixed cost to
/// account for the overhead of processing each command.
translation_per_command_base_charge: Option<u64>,

/// The base charge for each input in a programmable transaction regardless of if it is used or
/// not, or a pure/object/funds withdrawal input.
translation_per_input_base_charge: Option<u64>,

/// The base charge for each byte of pure input in a programmable transaction.
translation_pure_input_per_byte_charge: Option<u64>,

/// The multiplier for the number of type nodes when charging for type loading.
/// This is multiplied by the number of type nodes to get the total cost.
/// This should be a small number to avoid excessive gas costs for loading types.
translation_per_type_node_charge: Option<u64>,

/// The multiplier for the number of type references when charging for type checking and reference
/// checking.
translation_per_reference_node_charge: Option<u64>,

/// The multiplier for each linkage entry when charging for linkage tables that we have
/// created.
translation_per_linkage_entry_charge: Option<u64>,

/// The maximum number of updates per settlement transaction.
max_updates_per_settlement_txn: Option<u32>,

/// Maximum computation units allowed for a gasless transaction.
gasless_max_computation_units: Option<u64>,

/// Allowed token types for gasless transactions, with minimum transfer sizes per token.
#[skip_accessor]
gasless_allowed_token_types: Option<Vec<(String, u64)>>,

/// Maximum number of unused Pure inputs allowed in a gasless transaction.
/// Object and FundsWithdrawal inputs must always be used.
/// When None, there is no limit (effectively unlimited).
gasless_max_unused_inputs: Option<u64>,

/// Maximum size in bytes of each Pure input in a gasless transaction.
/// When None, there is no limit (effectively unlimited).
gasless_max_pure_input_bytes: Option<u64>,

/// Max tps for gasless transactions. Unlimited when unset, zero when set to zero.
gasless_max_tps: Option<u64>,

#[serde(skip_serializing_if = "Option::is_none")]
#[skip_accessor]
include_special_package_amendments: Option<Arc<Amendments>>,

/// Maximum serialized size in bytes of a gasless transaction (SenderSignedData).
/// Bounds the persistent storage impact of each admitted gasless transaction.
gasless_max_tx_size_bytes: Option<u64>,
}

추첨 winner나 lottery number를 고르는 경우처럼 randomness와 무관하게 실행되는 code에서는 이 공격이 문제가 되지 않는 경우가 많다. 하지만 이 공격이 문제가 될 수 있는 다른 사용 사례에서는 다음 접근법 중 하나를 고려한다.

logic 분리​

서로 다른 transaction이 호출해야 하는 2개의 함수로 로직을 나눈다.

  1. 첫 번째 transaction tx1은 첫 번째 함수를 호출하여 random value를 가져오고 이를 tx1의 다른 command가 읽을 수 없는 object에 저장한다. 예를 들어 이 함수는 object를 caller에게 transfer하거나 transaction digest를 저장하고 읽을 때 그것이 다름을 확인할 수 있다.

  2. 두 번째 transaction tx2는 두 번째 함수를 호출해 저장된 값을 읽고 작업을 완료한다. tx2는 실패할 수 있지만 이제 random value는 고정되었고 반복 호출로 수정할 수 없다.

공격자가 tx1이 commit한 randomness를 본 뒤 입력을 수정할 수 있으므로, 두 번째 함수의 입력이 고정되어 있고 tx1 이후에 수정될 수 없다는 점이 중요하다.

두 번째 단계가 끝내 완료되지 않는 경우를 우아하게 처리해야 한다. 예를 들어 첫 번째 단계에서 fee를 청구하는 방법으로 이를 달성할 수 있다.

Click to open

Example

entry fun reveal_alternative2_step1(nft: AirDropNFT, r: &Random, ctx: &mut TxContext) {
destroy_airdrop_nft(nft);

let mut generator = new_generator(r, ctx);
let v = generator.generate_u8_in_range(1, 100);

transfer::public_transfer(
RandomnessNFT { id: object::new(ctx), value: v },
ctx.sender(),
);
}
public fun reveal_alternative2_step2(nft: RandomnessNFT, ctx: &mut TxContext): MetalNFT {
let RandomnessNFT { id, value } = nft;
delete(id);

let metal = if (value <= 10) GOLD
else if (10 < value && value <= 40) SILVER
else BRONZE;

MetalNFT {
id: object::new(ctx),
metal,
}
}

resource usage​

주요 처리 경로가 무거운 작업을 수행하고 early-exit 경로는 빠르게 반환하도록 함수를 작성한다. 다음 사항을 염두에 둔다:

  • external function과 native function 모두 미래에 바뀔 수 있으며, 그 결과 test를 수행했을 때와 다른 cost가 생길 수 있다.

  • transaction cost를 벤치마크하려면 Profile the transaction을 사용한다.

  • 같은 transaction에서 생성되고 삭제된 UID는 생성되거나 삭제된 UID 수 제한에 포함되지 않는다.

Use (non-public) entry functions​

composition은 smart contract에 매우 강력하지만 randomness를 사용하는 함수에 대한 공격의 문도 연다.

예를 들어 dice를 굴리기 위해 randomness를 사용하는 betting game을 생각해 보자:

module games::dice {
...
public enum Ticket has drop {
Lost,
Won,
}

public fun is_winner(t: &Ticket): bool {
match (t) {
Ticket::Won => true,
Ticket::Lost => false,
}
}

/// If you guess correctly the output, then you get a GuessedCorrectly object.
/// Otherwise you get nothing.
public fun play_dice(guess: u8, fee: Coin<SUI>, r: &Random, ctx: &mut TxContext): Ticket {
// Pay for the turn
assert!(coin::value(&fee) == 1000000, EInvalidAmount);
transfer::public_transfer(fee, CREATOR_ADDRESS);

// Roll the dice
let mut generator = new_generator(r, ctx);
if (guess == generator.generate_u8_in_range(1, 6)) {
Ticket::Won
} else {
Ticket::Lost
}
}
...
}

공격자는 다음 함수를 배포할 수 있다:

public fun attack(guess: u8, r: &Random, ctx: &mut TxContext): Ticket {
let t = dice::play_dice(guess, r, ctx);
// revert the transaction if play_dice lost
assert!(!dice::is_winner(&t), 0);
t
}

이제 공격자는 추측값과 함께 attack을 호출하고 추측이 틀렸을 때는 fee transfer를 항상 revert할 수 있다. composition attack으로부터 보호하려면 함수를 private entry function으로 정의하여 다른 module의 함수가 이를 호출할 수 없게 한다.

Move compiler는 public을 인자로 받는 Random 함수를 거부함으로써 이 동작을 강제한다.

Programmable transaction block(PTB) 제한​

앞서 설명한 것과 비슷한 공격은 play_dice가 private entry function으로 정의되어 있더라도 PTB를 통해 가능하다. 예를 들어 앞서 정의한 entry play_dice(guess: u8, fee: Coin<SUI>, r: &Random, ctx: &mut TxContext): Ticket { … } 함수를 생각해 보면, 공격자는 다음 함수를 게시하고 첫 번째 command의 출력인 play_dice(...), attack(Result(0))를 사용해 Result(0) command를 가진 PTB를 보낼 수 있다:

public fun attack(t: Ticket): Ticket {
assert!(!dice::is_winner(&t), 0);
t
}

이 공격은 PTB의 atomic한 성질을 악용해 추측이 틀렸다면 fee를 지불하지 않고 전체 transaction을 항상 revert한다. 여러 transaction을 보내면 공격을 반복할 수 있고, 각 transaction은 서로 다른 randomness로 실행되며 추측이 틀리면 revert된다.

팁

PTB 기반 composition attack으로부터 보호하기 위해 Sui는 TransferObjects을 입력으로 사용하는 MergeCoins command 뒤에 MoveCall나 Random가 아닌 command가 오는 PTB를 거부한다.

Instantiating RandomGenerator​

RandomGenerator는 이를 소비하는 module이 생성하는 한 안전하다. 이를 인자로 전달하면 caller가 예를 들어 RandomGenerator를 호출하고 internal state를 parsing하여 그 bcs::to_bytes(&generator) instance의 출력을 예측할 수 있다.

Move compiler는 public를 인자로 받는 RandomGenerator 함수를 거부함으로써 이 동작을 강제한다.

Accessing Random from TypeScript​

module roll_dice(r: &Random, ctx: &mut TxContext)에서 example를 호출하려면 다음 코드를 사용한다:

const tx = new Transaction();
tx.moveCall({
target: "${PACKAGE_ID}::example::roll_dice",
arguments: [tx.object.random()]
});
...

모범 사례​

random number에 접근할 수 있다는 것은 secure application을 설계하는 일의 한 부분일 뿐이다. randomness를 어떻게 사용하는지도 세심하게 주의해야 한다.

randomness에 안전하게 접근하려면:

  • 함수를 (private) entry로 정의한다.

  • 함수 로컬 RandomGenerator를 사용해 randomness를 생성하는 방식을 선호한다.