Skip to content

Commit

Permalink
Community wallet transfer transaction script (#859)
Browse files Browse the repository at this point in the history
* reproduced error

* more specific error codes

* transfer community wallet should fail on normal tx api

* implement txs subcommand to execute community transfer tx script.

* community wallet script test pass.

* create integration test

* add changelog

* storage helper not initializing genesis waypoint

* patch tx script for trying to schedule payment to non-slow wallet

* mark payments as processed

* patch processing the finalized transfer state

* patch tests for error code change

* fix bug where query was resetting approved list.

* txs errors should return the context of the error

* more descriptive error messages for txs cli
  • Loading branch information
0o-de-lally authored Nov 26, 2021
1 parent d0945ac commit 6d7d7d1
Show file tree
Hide file tree
Showing 43 changed files with 1,069 additions and 436 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ debug:

##### DEVNET TESTS #####

devnet: clear fix fix-genesis dev-wizard start
devnet: clear fix dev-wizard dev-genesis start
# runs a smoke test from fixtures.
# Uses genesis blob from fixtures, assumes 3 validators, and test settings.
# This will work for validator nodes alice, bob, carol, and any fullnodes; 'eve'
Expand Down
6 changes: 3 additions & 3 deletions config/management/genesis/src/ol_node_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ pub fn write_node_config_files(
}
};

// storage_helper
// .insert_waypoint(&namespace, genesis_waypoint)
// .unwrap();
storage_helper
.insert_waypoint(&namespace, genesis_waypoint)
.unwrap();

// Write the genesis waypoint without a namespaced storage.
let mut disk_storage = OnDiskStorageConfig::default();
Expand Down
104 changes: 68 additions & 36 deletions language/diem-framework/modules/0L/Wallet.move
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ module Wallet {
const APPROVED: u8 = 1;
const REJECTED: u8 = 2;

const EIS_NOT_SLOW_WALLET: u64 = 0231010;

//////// COMMUNITY WALLETS ////////

struct CommunityWalletList has key {
Expand Down Expand Up @@ -122,6 +124,12 @@ module Wallet {
public fun new_timed_transfer(
sender: &signer, payee: address, value: u64, description: vector<u8>
): u64 acquires CommunityTransfers, CommunityWalletList {
// firstly check if payee is a slow wallet
// TODO: This function should check if the account is a slow wallet before sending
// but there's a circular dependency with DiemAccount which has the slow wallet struct.
// curretly we move that check to the transaction script to initialize the payment.
// assert(DiemAccount::is_slow(payee), EIS_NOT_SLOW_WALLET);

let sender_addr = Signer::address_of(sender);
let list = get_comm_list();
assert(
Expand Down Expand Up @@ -152,34 +160,6 @@ module Wallet {
Vector::push_back<TimedTransfer>(&mut transfers.proposed, t);
return transfers.max_uid
}

// Todo: Can be private, used only in tests
// Utlity to query a CommunityWallet transfer wallet.
// Note: does not need to be a public function, except for use in tests.
public fun find(
uid: u64,
type_of: u8
): (Option<TimedTransfer>, u64) acquires CommunityTransfers {
let c = borrow_global<CommunityTransfers>(@0x0);
let list = if (type_of == 0) {
&c.proposed
} else if (type_of == 1) {
&c.approved
} else {
&c.rejected
};

let len = Vector::length(list);
let i = 0;
while (i < len) {
let t = *Vector::borrow<TimedTransfer>(list, i);
if (t.uid == uid) {
return (Option::some<TimedTransfer>(t), i)
};
i = i + 1;
};
(Option::none<TimedTransfer>(), 0)
}

// A validator casts a vote to veto a proposed/pending transaction
// by a community wallet.
Expand Down Expand Up @@ -232,6 +212,32 @@ module Wallet {

}

// private function. Once vetoed, the CommunityWallet transaction is
// removed from proposed list.
public fun mark_processed(vm: &signer, t: TimedTransfer) acquires CommunityTransfers {
CoreAddresses::assert_vm(vm);

let c = borrow_global_mut<CommunityTransfers>(@0x0);
let list = *&c.proposed;
let len = Vector::length(&list);
let i = 0;
while (i < len) {
let search = *Vector::borrow<TimedTransfer>(&list, i);
if (search.uid == t.uid) {
Vector::remove<TimedTransfer>(&mut c.proposed, i);
Vector::push_back(&mut c.approved, search);
};

i = i + 1;
};

}

public fun reset_rejection_counter(vm: &signer, wallet: address) acquires CommunityFreeze {
CoreAddresses::assert_diem_root(vm);
borrow_global_mut<CommunityFreeze>(wallet).consecutive_rejections = 0;
}

// private function to tally vetos.
// checks if a voter is in the validator set.
// tallies everytime called. Only counts votes in the validator set.
Expand Down Expand Up @@ -283,9 +289,8 @@ module Wallet {
// Utility to list CommunityWallet transfers due, by epoch. Anyone can call this.
// This is used by VM in DiemAccount at epoch boundaries to process the wallet transfers.
public fun list_tx_by_epoch(epoch: u64): vector<TimedTransfer> acquires CommunityTransfers {
let c = borrow_global_mut<CommunityTransfers>(@0x0);
// reset approved list
c.approved = Vector::empty<TimedTransfer>();
let c = borrow_global<CommunityTransfers>(@0x0);

// loop proposed list
let pending = Vector::empty<TimedTransfer>();
let len = Vector::length(&c.proposed);
Expand All @@ -295,19 +300,46 @@ module Wallet {
if (t.expire_epoch == epoch) {

Vector::push_back<TimedTransfer>(&mut pending, *t);
// TODO: clear the freeze count on community wallet
// add to approved list
};
i = i + 1;
};
return pending
}

public fun reset_rejection_counter(vm: &signer, wallet: address) acquires CommunityFreeze {
CoreAddresses::assert_diem_root(vm);
borrow_global_mut<CommunityFreeze>(wallet).consecutive_rejections = 0;

public fun list_transfers(type_of: u8): vector<TimedTransfer> acquires CommunityTransfers {
let c = borrow_global<CommunityTransfers>(@0x0);
if (type_of == 0) {
*&c.proposed
} else if (type_of == 1) {
*&c.approved
} else {
*&c.rejected
}
}

// Todo: Can be private, used only in tests
// Utlity to query a CommunityWallet transfer wallet.
// Note: does not need to be a public function, except for use in tests.
public fun find(
uid: u64,
type_of: u8
): (Option<TimedTransfer>, u64) acquires CommunityTransfers {
let list = &list_transfers(type_of);

let len = Vector::length(list);
let i = 0;
while (i < len) {
let t = *Vector::borrow<TimedTransfer>(list, i);
if (t.uid == uid) {
return (Option::some<TimedTransfer>(t), i)
};
i = i + 1;
};
(Option::none<TimedTransfer>(), 0)
}


// Private function to freeze a community wallet
// community wallets get frozen if 3 consecutive attempts to transfer are rejected.
fun maybe_freeze(wallet: address) acquires CommunityFreeze {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module TransferScripts {
use 0x1::GAS::GAS;
use 0x1::Globals;
use 0x1::Signer;
use 0x1::Wallet;

public(script) fun balance_transfer(
sender: signer,
Expand All @@ -25,5 +26,26 @@ module TransferScripts {
assert(DiemAccount::balance<GAS>(sender_addr) < sender_balance_pre, 02);
}


public(script) fun community_transfer(
sender: signer,
destination: address,
unscaled_value: u64,
memo: vector<u8>,
) {
// IMPORTANT: the human representation of a value is unscaled. The user which expects to send 10 coins, will input that as an unscaled_value. This script converts it to the Move internal scale by multiplying by COIN_SCALING_FACTOR.
let value = unscaled_value * Globals::get_coin_scaling_factor();
let sender_addr = Signer::address_of(&sender);
assert(Wallet::is_comm(sender_addr), 0);

// confirm the destination account has a slow wallet
// TODO: this check only happens in this script since there's
// a circular dependecy issue with DiemAccount and Wallet which impedes
// checking in Wallet module
assert(DiemAccount::is_slow(destination), 1);

let uid = Wallet::new_timed_transfer(&sender, destination, value, memo);
assert(Wallet::transfer_is_proposed(uid), 2);
}
}
}
16 changes: 12 additions & 4 deletions language/diem-framework/modules/DiemAccount.move
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ module DiemAccount {

//////// 0L ////////
const EBELOW_MINIMUM_VALUE_BOOTSTRAP_COIN: u64 = 120125;
const EWITHDRAWAL_NOT_FOR_COMMUNITY_WALLET: u64 = 120126;
const EWITHDRAWAL_TRANSFERS_DISABLED_SYSTEMWIDE: u64 = 120127;
const EWITHDRAWAL_SLOW_WAL_EXCEEDS_UNLOCKED_LIMIT: u64 = 120128;



/////// 0L end /////////

Expand Down Expand Up @@ -485,10 +490,12 @@ module DiemAccount {
add_currencies_for_account<GAS>(&new_signer, false);
make_account(new_signer, new_account_authkey_prefix);

// if the initial coin sent is the minimum amount, don't check transfer limits.
if (value <= BOOTSTRAP_COIN_VALUE) {
onboarding_gas_transfer<GAS>(sender, new_account, value);
new_account
}
// otherwise, if the onboarder wants to send more, then it must respect the transfer limits.
else {
let with_cap = extract_withdraw_capability(sender);
pay_from<GAS>(
Expand Down Expand Up @@ -1232,14 +1239,14 @@ module DiemAccount {
let community_wallets = Wallet::get_comm_list();
assert(
!Vector::contains(&community_wallets, &sender_addr),
Errors::limit_exceeded(EWITHDRAWAL_EXCEEDS_LIMITS)
Errors::limit_exceeded(EWITHDRAWAL_NOT_FOR_COMMUNITY_WALLET)
);
/////// 0L /////////
if (!DiemConfig::check_transfer_enabled()) {
// only VM can make TXs if transfers are not enabled.
assert(
sender_addr == CoreAddresses::DIEM_ROOT_ADDRESS(),
Errors::limit_exceeded(EWITHDRAWAL_EXCEEDS_LIMITS)
Errors::limit_exceeded(EWITHDRAWAL_TRANSFERS_DISABLED_SYSTEMWIDE)
);
};
// Abort if we already extracted the unique withdraw capability for this account.
Expand Down Expand Up @@ -1372,12 +1379,13 @@ module DiemAccount {
let t: Wallet::TimedTransfer = *Vector::borrow(&v, i);
// TODO: Is this the best way to access a struct property from
// outside a module?
let (payer, payee, value, description) = Wallet::get_tx_args(t);
let (payer, payee, value, description) = Wallet::get_tx_args(*&t);
if (Wallet::is_frozen(payer)) {
i = i + 1;
continue
};
vm_make_payment_no_limit<GAS>(payer, payee, value, description, b"", vm);
Wallet::mark_processed(vm, t);
Wallet::reset_rejection_counter(vm, payer);
i = i + 1;
};
Expand Down Expand Up @@ -1492,7 +1500,7 @@ module DiemAccount {
if (is_slow(*&cap.account_address)) {
assert(
amount < unlocked_amount(*&cap.account_address),
Errors::limit_exceeded(EWITHDRAWAL_EXCEEDS_LIMITS)
Errors::limit_exceeded(EWITHDRAWAL_SLOW_WAL_EXCEEDS_UNLOCKED_LIMIT)
);

};
Expand Down
38 changes: 34 additions & 4 deletions language/diem-framework/modules/doc/DiemAccount.md
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,33 @@ The withdrawal of funds would have exceeded the the account's limits



<a name="0x1_DiemAccount_EWITHDRAWAL_NOT_FOR_COMMUNITY_WALLET"></a>



<pre><code><b>const</b> <a href="DiemAccount.md#0x1_DiemAccount_EWITHDRAWAL_NOT_FOR_COMMUNITY_WALLET">EWITHDRAWAL_NOT_FOR_COMMUNITY_WALLET</a>: u64 = 120126;
</code></pre>



<a name="0x1_DiemAccount_EWITHDRAWAL_SLOW_WAL_EXCEEDS_UNLOCKED_LIMIT"></a>



<pre><code><b>const</b> <a href="DiemAccount.md#0x1_DiemAccount_EWITHDRAWAL_SLOW_WAL_EXCEEDS_UNLOCKED_LIMIT">EWITHDRAWAL_SLOW_WAL_EXCEEDS_UNLOCKED_LIMIT</a>: u64 = 120128;
</code></pre>



<a name="0x1_DiemAccount_EWITHDRAWAL_TRANSFERS_DISABLED_SYSTEMWIDE"></a>



<pre><code><b>const</b> <a href="DiemAccount.md#0x1_DiemAccount_EWITHDRAWAL_TRANSFERS_DISABLED_SYSTEMWIDE">EWITHDRAWAL_TRANSFERS_DISABLED_SYSTEMWIDE</a>: u64 = 120127;
</code></pre>



<a name="0x1_DiemAccount_EWITHDRAW_CAPABILITY_ALREADY_EXTRACTED"></a>

The <code><a href="DiemAccount.md#0x1_DiemAccount_WithdrawCapability">WithdrawCapability</a></code> for this account has already been extracted
Expand Down Expand Up @@ -1444,10 +1471,12 @@ Initialize this module. This is only callable from genesis.
<a href="DiemAccount.md#0x1_DiemAccount_add_currencies_for_account">add_currencies_for_account</a>&lt;<a href="GAS.md#0x1_GAS">GAS</a>&gt;(&new_signer, <b>false</b>);
<a href="DiemAccount.md#0x1_DiemAccount_make_account">make_account</a>(new_signer, new_account_authkey_prefix);

// <b>if</b> the initial coin sent is the minimum amount, don't check transfer limits.
<b>if</b> (value &lt;= <a href="DiemAccount.md#0x1_DiemAccount_BOOTSTRAP_COIN_VALUE">BOOTSTRAP_COIN_VALUE</a>) {
<a href="DiemAccount.md#0x1_DiemAccount_onboarding_gas_transfer">onboarding_gas_transfer</a>&lt;<a href="GAS.md#0x1_GAS">GAS</a>&gt;(sender, new_account, value);
new_account
}
// otherwise, <b>if</b> the onboarder wants <b>to</b> send more, then it must respect the transfer limits.
<b>else</b> {
<b>let</b> with_cap = <a href="DiemAccount.md#0x1_DiemAccount_extract_withdraw_capability">extract_withdraw_capability</a>(sender);
<a href="DiemAccount.md#0x1_DiemAccount_pay_from">pay_from</a>&lt;<a href="GAS.md#0x1_GAS">GAS</a>&gt;(
Expand Down Expand Up @@ -2638,14 +2667,14 @@ the sender's account balance.
<b>let</b> community_wallets = <a href="Wallet.md#0x1_Wallet_get_comm_list">Wallet::get_comm_list</a>();
<b>assert</b>(
!<a href="../../../../../../move-stdlib/docs/Vector.md#0x1_Vector_contains">Vector::contains</a>(&community_wallets, &sender_addr),
<a href="../../../../../../move-stdlib/docs/Errors.md#0x1_Errors_limit_exceeded">Errors::limit_exceeded</a>(<a href="DiemAccount.md#0x1_DiemAccount_EWITHDRAWAL_EXCEEDS_LIMITS">EWITHDRAWAL_EXCEEDS_LIMITS</a>)
<a href="../../../../../../move-stdlib/docs/Errors.md#0x1_Errors_limit_exceeded">Errors::limit_exceeded</a>(<a href="DiemAccount.md#0x1_DiemAccount_EWITHDRAWAL_NOT_FOR_COMMUNITY_WALLET">EWITHDRAWAL_NOT_FOR_COMMUNITY_WALLET</a>)
);
/////// 0L /////////
<b>if</b> (!<a href="DiemConfig.md#0x1_DiemConfig_check_transfer_enabled">DiemConfig::check_transfer_enabled</a>()) {
// only VM can make TXs <b>if</b> transfers are not enabled.
<b>assert</b>(
sender_addr == <a href="CoreAddresses.md#0x1_CoreAddresses_DIEM_ROOT_ADDRESS">CoreAddresses::DIEM_ROOT_ADDRESS</a>(),
<a href="../../../../../../move-stdlib/docs/Errors.md#0x1_Errors_limit_exceeded">Errors::limit_exceeded</a>(<a href="DiemAccount.md#0x1_DiemAccount_EWITHDRAWAL_EXCEEDS_LIMITS">EWITHDRAWAL_EXCEEDS_LIMITS</a>)
<a href="../../../../../../move-stdlib/docs/Errors.md#0x1_Errors_limit_exceeded">Errors::limit_exceeded</a>(<a href="DiemAccount.md#0x1_DiemAccount_EWITHDRAWAL_TRANSFERS_DISABLED_SYSTEMWIDE">EWITHDRAWAL_TRANSFERS_DISABLED_SYSTEMWIDE</a>)
);
};
// Abort <b>if</b> we already extracted the unique withdraw capability for this account.
Expand Down Expand Up @@ -2782,12 +2811,13 @@ Return the withdraw capability to the account it originally came from
<b>let</b> t: <a href="Wallet.md#0x1_Wallet_TimedTransfer">Wallet::TimedTransfer</a> = *<a href="../../../../../../move-stdlib/docs/Vector.md#0x1_Vector_borrow">Vector::borrow</a>(&v, i);
// TODO: Is this the best way <b>to</b> access a <b>struct</b> property from
// outside a <b>module</b>?
<b>let</b> (payer, payee, value, description) = <a href="Wallet.md#0x1_Wallet_get_tx_args">Wallet::get_tx_args</a>(t);
<b>let</b> (payer, payee, value, description) = <a href="Wallet.md#0x1_Wallet_get_tx_args">Wallet::get_tx_args</a>(*&t);
<b>if</b> (<a href="Wallet.md#0x1_Wallet_is_frozen">Wallet::is_frozen</a>(payer)) {
i = i + 1;
<b>continue</b>
};
<a href="DiemAccount.md#0x1_DiemAccount_vm_make_payment_no_limit">vm_make_payment_no_limit</a>&lt;<a href="GAS.md#0x1_GAS">GAS</a>&gt;(payer, payee, value, description, b"", vm);
<a href="Wallet.md#0x1_Wallet_mark_processed">Wallet::mark_processed</a>(vm, t);
<a href="Wallet.md#0x1_Wallet_reset_rejection_counter">Wallet::reset_rejection_counter</a>(vm, payer);
i = i + 1;
};
Expand Down Expand Up @@ -2958,7 +2988,7 @@ subject to the dual attestation protocol
<b>if</b> (<a href="DiemAccount.md#0x1_DiemAccount_is_slow">is_slow</a>(*&cap.account_address)) {
<b>assert</b>(
amount &lt; <a href="DiemAccount.md#0x1_DiemAccount_unlocked_amount">unlocked_amount</a>(*&cap.account_address),
<a href="../../../../../../move-stdlib/docs/Errors.md#0x1_Errors_limit_exceeded">Errors::limit_exceeded</a>(<a href="DiemAccount.md#0x1_DiemAccount_EWITHDRAWAL_EXCEEDS_LIMITS">EWITHDRAWAL_EXCEEDS_LIMITS</a>)
<a href="../../../../../../move-stdlib/docs/Errors.md#0x1_Errors_limit_exceeded">Errors::limit_exceeded</a>(<a href="DiemAccount.md#0x1_DiemAccount_EWITHDRAWAL_SLOW_WAL_EXCEEDS_UNLOCKED_LIMIT">EWITHDRAWAL_SLOW_WAL_EXCEEDS_UNLOCKED_LIMIT</a>)
);

};
Expand Down
Loading

0 comments on commit 6d7d7d1

Please sign in to comment.