// The module for the account resource that governs every Libra account module LibraAccount { import 0x0.LibraCoin; import 0x00.Hash; import 0x0.Event; import 0x0.U64Util; import 0x0.AddressUtil; import 0x0.BytearrayUtil; // Every Libra account has a LibraAccount.T resource resource T { // The current authentication key. // This can be different than the key used to create the account authentication_key: bytearray, // The coins stored in this account balance: LibraCoin.T, // If true, the authority to rotate the authentication key of this account resides elsewhere delegated_key_rotation_capability: bool, // If true, the authority to withdraw funds from this account resides elsewhere delegated_withdrawal_capability: bool, // Event handle for received event received_events: Event.Handle, // Event handle for sent event sent_events: Event.Handle, // The current sequence number. // Incremented by one each time a transaction is submitted sequence_number: u64, } // The holder of WithdrawalCapability for account_address can withdraw Libra from // account_address/LibraAccount.T/balance. // There is at most one WithdrawalCapability in existence for a given address. resource WithdrawalCapability { account_address: address, } // The holder of KeyRotationCapability for account_address can rotate the authentication key for // account_address (i.e., write to account_address/LibraAccount.T/authentication_key). // There is at most one KeyRotationCapability in existence for a given address. resource KeyRotationCapability { account_address: address, } // Message for sent events struct SentPaymentEvent { // The amount of LibraCoin.T sent amount: u64, // The address that was paid payee: address, } // Message for received events struct ReceivedPaymentEvent { // The amount of LibraCoin.T received amount: u64, // The address that sent the coin payer: address, } // Creates a new LibraAccount.T // Invoked by the `create_account` builtin make(auth_key: bytearray): Self.T { let zero_balance: LibraCoin.T; let sent_handle: Event.Handle; let received_handle: Event.Handle; sent_handle = Event.new_event_handle(); received_handle = Event.new_event_handle(); zero_balance = LibraCoin.zero(); return T { authentication_key: move(auth_key), balance: move(zero_balance), delegated_key_rotation_capability: false, delegated_withdrawal_capability: false, received_events: move(received_handle), sent_events: move(sent_handle), sequence_number: 0, }; } // Deposits the `to_deposit` coin into the `payee`'s account public deposit(payee: address, to_deposit: LibraCoin.T) acquires T { let deposit_value: u64; let payee_account_ref: &mut Self.T; let sender: address; let sender_account_ref: &mut Self.T; let sent_event: Self.SentPaymentEvent; let received_event: Self.ReceivedPaymentEvent; let sent_event_bytes: bytearray; let received_event_bytes: bytearray; // Check that the `to_deposit` coin is non-zero deposit_value = LibraCoin.value(&to_deposit); assert(copy(deposit_value) > 0, 7); // Load the sender's account sender = get_txn_sender(); sender_account_ref = borrow_global_mut(copy(sender)); // Log a send event sent_event = SentPaymentEvent { amount: copy(deposit_value), payee: copy(payee) }; Event.emit_event(&mut move(sender_account_ref).sent_events, move(sent_event)); // Load the payee's account payee_account_ref = borrow_global_mut(move(payee)); // Deposit the `to_deposit` coin LibraCoin.deposit(&mut copy(payee_account_ref).balance, move(to_deposit)); // Log a received event received_event = ReceivedPaymentEvent { amount: move(deposit_value), payer: move(sender) }; Event.emit_event(&mut move(payee_account_ref).received_events, move(received_event)); return; } // mint_to_address can only be called by accounts with MintCapability (see LibraCoin) // and those account will be charged for gas. If those account don't have enough gas to pay // for the transaction cost they will fail minting. // However those account can also mint to themselves so that is a decent workaround public mint_to_address(payee: address, amount: u64) acquires T { // Create an account if it does not exist if (!exists(copy(payee))) { Self.create_new_account(copy(payee), 0); } // Mint and deposit the coin Self.deposit(move(payee), LibraCoin.mint_with_default_capability(move(amount))); return; } // Helper to withdraw `amount` from the given `account` and return the resulting LibraCoin.T withdraw_from_account(account: &mut Self.T, amount: u64): LibraCoin.T { let to_withdraw: LibraCoin.T; to_withdraw = LibraCoin.withdraw(&mut move(account).balance, copy(amount)); return move(to_withdraw); } // Withdraw `amount` LibraCoin.T from the transaction sender's account public withdraw_from_sender(amount: u64): LibraCoin.T acquires T { let sender_account: &mut Self.T; sender_account = borrow_global_mut(get_txn_sender()); if (*©(sender_account).delegated_withdrawal_capability) { // The sender has delegated the privilege to withdraw from her account elsewhere--abort. abort(11); } else { // The sender has retained her withdrawal privileges--proceed. return Self.withdraw_from_account(move(sender_account), move(amount)); } } // Withdraw `amount` LibraCoin.T from account under cap.account_address public withdraw_with_capability( cap: &Self.WithdrawalCapability, amount: u64 ): LibraCoin.T acquires T { let account: &mut Self.T; account = borrow_global_mut(*&move(cap).account_address); return Self.withdraw_from_account(move(account), move(amount)); } // Return a unique capability granting permission to withdraw from the sender's account balance. public extract_sender_withdrawal_capability(): Self.WithdrawalCapability acquires T { let sender: address; let sender_account: &mut Self.T; let delegated_ref: &mut bool; sender = get_txn_sender(); sender_account = borrow_global_mut(copy(sender)); delegated_ref = &mut move(sender_account).delegated_withdrawal_capability; if (*copy(delegated_ref)) { // We already extracted the unique withdrawal capability for this account. abort(11); } else { *move(delegated_ref) = true; // ensure uniqueness of the capability return WithdrawalCapability { account_address: move(sender) }; } } // Return the withdrawal capability to the account it originally came from public restore_withdrawal_capability(cap: Self.WithdrawalCapability) acquires T { let account_address: address; let account: &mut Self.T; // Destroy the capability WithdrawalCapability { account_address } = move(cap); account = borrow_global_mut(move(account_address)); // Update the flag for `account_address` to indicate that the capability has been restored. // The account owner will now be able to call pay_from_sender, withdraw_from_sender, and // extract_sender_withdrawal_capability again. *(&mut move(account).delegated_withdrawal_capability) = false; return; } // Withdraw `amount` LibraCoin.T from the transaction sender's account and send the coin // to the `payee` address // Creates the `payee` account if it does not exist public pay_from_sender(payee: address, amount: u64) acquires T { let to_pay: LibraCoin.T; if (exists(copy(payee))) { to_pay = Self.withdraw_from_sender(move(amount)); Self.deposit(move(payee), move(to_pay)); } else { Self.create_new_account(move(payee), move(amount)); } return; } rotate_authentication_key_for_account(account: &mut Self.T, new_authentication_key: bytearray) { *(&mut move(account).authentication_key) = move(new_authentication_key); return; } // Rotate the transaction sender's authentication key // The new key will be used for signing future transactions public rotate_authentication_key(new_authentication_key: bytearray) acquires T { let sender_account: &mut Self.T; sender_account = borrow_global_mut(get_txn_sender()); if (*©(sender_account).delegated_key_rotation_capability) { // The sender has delegated the privilege to rotate her key elsewhere--abort abort(11); } else { // The sender has retained her key rotation privileges--proceed. Self.rotate_authentication_key_for_account( move(sender_account), move(new_authentication_key) ); return; } } // Rotate the authentication key for the account under cap.account_address public rotate_authentication_key_with_capability( cap: &Self.KeyRotationCapability, new_authentication_key: bytearray, ) acquires T { Self.rotate_authentication_key_for_account( borrow_global_mut(*&move(cap).account_address), move(new_authentication_key) ); return; } // Return a unique capability granting permission to rotate the sender's authentication key public extract_sender_key_rotation_capability(): Self.KeyRotationCapability acquires T { let sender: address; let delegated_ref: &mut bool; sender = get_txn_sender(); delegated_ref = &mut borrow_global_mut(copy(sender)).delegated_key_rotation_capability; if (*copy(delegated_ref)) { // We already extracted the unique key rotation capability for this account. abort(11); } else { *move(delegated_ref) = true; // ensure uniqueness of the capability return KeyRotationCapability { account_address: move(sender) }; } } // Return the key rotation capability to the account it originally came from public restore_key_rotation_capability(cap: Self.KeyRotationCapability) acquires T { let account_address: address; let account: &mut Self.T; // Destroy the capability KeyRotationCapability { account_address } = move(cap); account = borrow_global_mut(move(account_address)); // Update the flag for `account_address` to indicate that the capability has been restored. // The account owner will now be able to call rotate_authentication_key and // extract_sender_key_rotation_capability again *(&mut move(account).delegated_key_rotation_capability) = false; return; } // Creates a new account at `fresh_address` with the `initial_balance` deducted from the // transaction sender's account public create_new_account(fresh_address: address, initial_balance: u64) acquires T { create_account(copy(fresh_address)); if (copy(initial_balance) > 0) { Self.pay_from_sender(move(fresh_address), move(initial_balance)); } return; } // Helper to return u64 value of the `balance` field for given `account` balance_for_account(account: &Self.T): u64 { let balance_value: u64; balance_value = LibraCoin.value(&move(account).balance); return move(balance_value); } // Return the current balance of the LibraCoin.T in LibraAccount.T at `addr` public balance(addr: address): u64 acquires T { return Self.balance_for_account(borrow_global(move(addr))); } // Helper to return the sequence number field for given `account` sequence_number_for_account(account: &Self.T): u64 { return *(&move(account).sequence_number); } // Return the current sequence number at `addr` public sequence_number(addr: address): u64 acquires T { return Self.sequence_number_for_account(borrow_global(move(addr))); } // Return true if the account at `addr` has delegated its key rotation capability public delegated_key_rotation_capability(addr: address): bool acquires T { return *&(borrow_global(move(addr))).delegated_key_rotation_capability; } // Return true if the account at `addr` has delegated its withdrawal capability public delegated_withdrawal_capability(addr: address): bool acquires T { return *&(borrow_global(move(addr))).delegated_withdrawal_capability; } // Return a reference to the address associated with the given withdrawal capability public withdrawal_capability_address(cap: &Self.WithdrawalCapability): &address { return &move(cap).account_address; } // Return a reference to the address associated with the given key rotation capability public key_rotation_capability_address(cap: &Self.KeyRotationCapability): &address { return &move(cap).account_address; } // Checks if an account exists at `check_addr` public exists(check_addr: address): bool { return exists(move(check_addr)); } // The prologue is invoked at the beginning of every transaction // It verifies: // - The account's auth key matches the transaction's public key // - That the account has enough balance to pay for all of the gas // - That the sequence number matches the transaction's sequence key prologue() acquires T { let transaction_sender: address; let transaction_sender_exists: bool; let sender_account: &mut Self.T; let imm_sender_account: &Self.T; let sender_public_key: bytearray; let public_key_hash: bytearray; let gas_price: u64; let gas_units: u64; let gas_fee: u64; let balance_amount: u64; let sequence_number_value: u64; let transaction_sequence_number_value: u64; transaction_sender = get_txn_sender(); // FUTURE: Make these error codes sequential // Verify that the transaction sender's account exists transaction_sender_exists = exists(copy(transaction_sender)); assert(move(transaction_sender_exists), 5); // Load the transaction sender's account sender_account = borrow_global_mut(copy(transaction_sender)); // Check that the transaction's public key matches the account's current auth key sender_public_key = get_txn_public_key(); public_key_hash = Hash.sha3_256(move(sender_public_key)); assert(move(public_key_hash) == *(©(sender_account).authentication_key), 2); // Check that the account has enough balance for all of the gas gas_price = get_txn_gas_unit_price(); gas_units = get_txn_max_gas_units(); gas_fee = move(gas_price) * move(gas_units); imm_sender_account = freeze(copy(sender_account)); balance_amount = Self.balance_for_account(move(imm_sender_account)); assert(move(balance_amount) >= move(gas_fee), 6); // Check that the transaction sequence number matches the sequence number of the account sequence_number_value = *(&mut move(sender_account).sequence_number); transaction_sequence_number_value = get_txn_sequence_number(); assert(copy(transaction_sequence_number_value) >= copy(sequence_number_value), 3); assert(move(transaction_sequence_number_value) == move(sequence_number_value), 4); return; } // The epilogue is invoked at the end of transactions. // It collects gas and bumps the sequence number epilogue() acquires T { let transaction_sender: address; let sender_account: &mut Self.T; let imm_sender_account: &Self.T; let gas_price: u64; let gas_units_remaining: u64; let starting_gas_units: u64; let gas_fee_amount: u64; let balance_amount: u64; let gas_fee: LibraCoin.T; let transaction_sequence_number_value: u64; transaction_sender = get_txn_sender(); // Load the transaction sender's account sender_account = borrow_global_mut(copy(transaction_sender)); // Charge for gas gas_price = get_txn_gas_unit_price(); starting_gas_units = get_txn_max_gas_units(); gas_units_remaining = get_gas_remaining(); gas_fee_amount = move(gas_price) * (move(starting_gas_units) - move(gas_units_remaining)); imm_sender_account = freeze(copy(sender_account)); balance_amount = Self.balance_for_account(move(imm_sender_account)); assert(move(balance_amount) >= copy(gas_fee_amount), 6); gas_fee = Self.withdraw_from_account(copy(sender_account), move(gas_fee_amount)); LibraCoin.TODO_REMOVE_burn_gas_fee(move(gas_fee)); // Bump the sequence number transaction_sequence_number_value = get_txn_sequence_number(); *(&mut move(sender_account).sequence_number) = move(transaction_sequence_number_value) + 1; return; } }