Skip to content

Commit

Permalink
transfer-contract: add fn transfer_to_account
Browse files Browse the repository at this point in the history
The `transfer_to_account` can be called by contract using the
`TransferToAccount` structure to move some of its funds to a Moonlight
account.
  • Loading branch information
Eduardo Leegwater Simões committed Sep 5, 2024
1 parent 7e9eb62 commit 5e0f043
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 4 deletions.
5 changes: 5 additions & 0 deletions contracts/transfer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ unsafe fn transfer_to_contract(arg_len: u32) -> u32 {
rusk_abi::wrap_call(arg_len, |arg| STATE.transfer_to_contract(arg))
}

#[no_mangle]
unsafe fn transfer_to_account(arg_len: u32) -> u32 {
rusk_abi::wrap_call(arg_len, |arg| STATE.transfer_to_account(arg))
}

// Queries

#[no_mangle]
Expand Down
39 changes: 37 additions & 2 deletions contracts/transfer/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ use execution_core::{
withdraw::{
Withdraw, WithdrawReceiver, WithdrawReplayToken, WithdrawSignature,
},
ReceiveFromContract, Transaction, TransferToContract,
PANIC_NONCE_NOT_READY, TRANSFER_CONTRACT,
ReceiveFromContract, Transaction, TransferToAccount,
TransferToContract, PANIC_NONCE_NOT_READY, TRANSFER_CONTRACT,
},
BlsScalar, ContractError, ContractId,
};
Expand Down Expand Up @@ -352,6 +352,41 @@ impl TransferState {
.expect("Calling receiver should succeed")
}

/// Transfer funds from a contract balance to a Moonlight account.
///
/// Contracts can call the function and expect that if it succeeds the funds
/// are successfully transferred to the account they specify.
///
/// # Panics
/// The function will panic if it is not being called by a contract, if it
/// is called by the transfer contract itself, or if the calling contract
/// doesn't have enough funds.
pub fn transfer_to_account(&mut self, transfer: TransferToAccount) {
let from = rusk_abi::caller()
.expect("A transfer to an account must happen in the context of a transaction");

if from == TRANSFER_CONTRACT {
panic!("Cannot be called directly by the transfer contract");
}

let from_balance = self
.contract_balances
.get_mut(&from)
.expect("Caller must have a balance");

if *from_balance < transfer.value {
panic!("Caller must have enough balance");
}

let account = self
.accounts
.entry(transfer.account.to_bytes())
.or_insert(EMPTY_ACCOUNT);

*from_balance -= transfer.value;
account.balance += transfer.value;
}

/// The top level transaction execution function.
///
/// This will emplace the deposit in the state, if it exists - making it
Expand Down
139 changes: 137 additions & 2 deletions contracts/transfer/tests/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use execution_core::{
ViewKey as PhoenixViewKey,
},
withdraw::{Withdraw, WithdrawReceiver, WithdrawReplayToken},
TransferToContract, TRANSFER_CONTRACT,
TransferToAccount, TransferToContract, TRANSFER_CONTRACT,
},
ContractId, JubJubScalar, LUX,
};
Expand Down Expand Up @@ -99,7 +99,7 @@ fn instantiate<Rng: RngCore + CryptoRng>(
ContractData::builder()
.owner(OWNER)
.contract_id(BOB_ID)
.constructor_arg(&1u8),
.init_arg(&1u8),
GAS_LIMIT,
)
.expect("Deploying the bob contract should succeed");
Expand Down Expand Up @@ -1058,3 +1058,138 @@ fn transfer_to_contract() {
"Bob must have the transfer value as balance"
);
}

/// In this test we deposit some Dusk from a moonlight account to the Alice
/// contract, and subsequently call the Alice contract to trigger a transfer
/// back to the same account.
#[test]
fn transfer_to_account() {
const DEPOSIT_VALUE: u64 = MOONLIGHT_GENESIS_VALUE / 2;
const TRANSFER_VALUE: u64 = DEPOSIT_VALUE / 2;

let rng = &mut StdRng::seed_from_u64(0xfeeb);

let vm = &mut rusk_abi::new_ephemeral_vm()
.expect("Creating ephemeral VM should work");

let phoenix_pk = PhoenixPublicKey::from(&PhoenixSecretKey::random(rng));

let moonlight_sk = AccountSecretKey::random(rng);
let moonlight_pk = AccountPublicKey::from(&moonlight_sk);

let session = &mut instantiate(rng, vm, &phoenix_pk, &moonlight_pk);

let acc = account(session, &moonlight_pk)
.expect("Getting the account should succeed");
let alice_balance = contract_balance(session, ALICE_ID)
.expect("Querying the contract balance should succeed");

assert_eq!(
acc.balance, MOONLIGHT_GENESIS_VALUE,
"The depositer account should have the genesis value"
);
assert_eq!(
alice_balance, 0,
"Alice must have an initial balance of zero"
);

let fn_args = rkyv::to_bytes::<_, 256>(&DEPOSIT_VALUE)
.expect("Serializing should succeed")
.to_vec();
let contract_call = Some(ContractCall {
contract: ALICE_ID,
fn_name: String::from("deposit"),
fn_args,
});

let chain_id =
chain_id(session).expect("Getting the chain ID should succeed");

let transaction = MoonlightTransaction::new(
&moonlight_sk,
None,
0,
DEPOSIT_VALUE,
GAS_LIMIT,
LUX,
acc.nonce + 1,
chain_id,
contract_call,
)
.expect("Creating moonlight transaction should succeed");

let receipt =
execute(session, transaction).expect("Transaction should succeed");
let gas_spent_deposit = receipt.gas_spent;

println!("MOONLIGHT DEPOSIT: {:?}", receipt.data);
println!("MOONLIGHT DEPOSIT: {gas_spent_deposit} gas");

let acc = account(session, &moonlight_pk)
.expect("Getting the account should succeed");
let alice_balance = contract_balance(session, ALICE_ID)
.expect("Querying the contract balance should succeed");

assert_eq!(
acc.balance,
MOONLIGHT_GENESIS_VALUE - gas_spent_deposit - DEPOSIT_VALUE,
"The account should decrease by the amount spent and the deposit sent"
);
assert_eq!(
alice_balance, DEPOSIT_VALUE,
"Alice must have the deposit in their balance"
);

let transfer = TransferToAccount {
account: moonlight_pk,
value: TRANSFER_VALUE,
};
let fn_args = rkyv::to_bytes::<_, 256>(&transfer)
.expect("Serializing should succeed")
.to_vec();
let contract_call = Some(ContractCall {
contract: ALICE_ID,
fn_name: String::from("transfer_to_account"),
fn_args,
});

let transaction = MoonlightTransaction::new(
&moonlight_sk,
None,
0,
0,
GAS_LIMIT,
LUX,
acc.nonce + 1,
chain_id,
contract_call,
)
.expect("Creating moonlight transaction should succeed");

let receipt =
execute(session, transaction).expect("Transaction should succeed");
let gas_spent_send = receipt.gas_spent;

println!("MOONLIGHT SEND_TO_ACCOUNT: {:?}", receipt.data);
println!("MOONLIGHT SEND_TO_ACCOUNT: {gas_spent_send} gas");

let acc = account(session, &moonlight_pk)
.expect("Getting the account should succeed");
let alice_balance = contract_balance(session, ALICE_ID)
.expect("Querying the contract balance should succeed");

assert_eq!(
acc.balance,
MOONLIGHT_GENESIS_VALUE
- gas_spent_deposit
- gas_spent_send
- DEPOSIT_VALUE
+ TRANSFER_VALUE,
"The account should decrease by the amount spent and the deposit sent, \
and increase by the transfer"
);
assert_eq!(
alice_balance, DEPOSIT_VALUE - TRANSFER_VALUE,
"Alice must have the deposit minus the transferred amount in their balance"
);
}

0 comments on commit 5e0f043

Please sign in to comment.