Skip to content

Commit

Permalink
transfer-contract: allow converting between Phoenix and Moonlight Dusk
Browse files Browse the repository at this point in the history
We add the `convert` function to the contract, leveraging the deposit
and withdrawal capabilities to atomically swap Dusk between the Phoenix
and Moonlight models.

The function is meant to be called directly - meaning as the first and
only call - and takes a `Withdraw` as an argument, such that the user
can prove ownership of either the account or address being deposited to.

Resolves: #1994
  • Loading branch information
Eduardo Leegwater Simões committed Jul 26, 2024
1 parent 8a9bbc5 commit 2a1822b
Show file tree
Hide file tree
Showing 4 changed files with 395 additions and 22 deletions.
5 changes: 5 additions & 0 deletions contracts/transfer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ unsafe fn withdraw(arg_len: u32) -> u32 {
rusk_abi::wrap_call(arg_len, |arg| STATE.withdraw(arg))
}

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

// Queries

#[no_mangle]
Expand Down
56 changes: 55 additions & 1 deletion contracts/transfer/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use alloc::vec::Vec;
use dusk_bytes::Serializable;
use poseidon_merkle::Opening as PoseidonOpening;
use ringbuffer::{ConstGenericRingBuffer, RingBuffer};
use rusk_abi::{ContractError, ContractId, STAKE_CONTRACT};
use rusk_abi::{ContractError, ContractId, STAKE_CONTRACT, TRANSFER_CONTRACT};

use execution_core::{
transfer::{
Expand Down Expand Up @@ -201,6 +201,60 @@ impl TransferState {
self.mint_withdrawal("WITHDRAW", withdraw);
}

/// Takes the deposit addressed to this contract, and immediately withdraws
/// it, effectively performing an atomic conversion between Phoenix notes
/// and Moonlight balance.
///
/// This functions checks whether the deposit included with the transaction
/// is the exact value included in `convert`, and imposes that the
/// caller is indeed this contract.
///
/// # Panics
/// This can only be called by this contract - the transfer contract - and
/// will panic if this is not the case.
pub fn convert(&mut self, convert: Withdraw) {
if rusk_abi::caller() != TRANSFER_CONTRACT {
panic!("Only a direct call can be a conversion");
}
if *convert.contract() != TRANSFER_CONTRACT.to_bytes() {
panic!("The conversion must target the transfer contract");
}

let deposit = transitory::deposit_info_mut();
match deposit {
Deposit::Available(_, deposit_value) => {
let deposit_value = *deposit_value;

if convert.value() != deposit_value {
panic!("The value to convert doesn't match the value in the transaction");
}

// This is a direct contract call, as the first check in this
// function indicates, and the target of a deposit is always the
// direct contract called. Therefore, there is no need to check
// the available deposit targets this contract.
//
// if deposit_contract != TRANSFER_CONTRACT {
// panic!();
// }

// Handle the withdrawal part of the conversion and set the
// deposit as being taken. Interesting to note is that we don't
// need to change the value held by the contract at all, since
// it never changes.
self.mint_withdrawal("CONVERT", convert);
*deposit = Deposit::Taken(TRANSFER_CONTRACT, deposit_value);
}
Deposit::None => panic!("There is no deposit in the transaction"),
// The transfer contract never calls this function directly, and the
// first check in this function is that the caller is the transfer
// contract. Therefore, the only way this code is reached is as a
// first contract call, and a deposit already being taken is
// impossible.
_ => unreachable!(),
}
}

/// Deposit funds to a contract's balance.
///
/// This function checks whether a deposit has been placed earlier on the
Expand Down
26 changes: 18 additions & 8 deletions contracts/transfer/tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ use execution_core::{
Note, PublicKey, SchnorrSecretKey, SecretKey, Sender, TxSkeleton, ViewKey,
};
use rusk_abi::{
ContractError, ContractId, PiecrustError, Session, TRANSFER_CONTRACT,
CallReceipt, ContractError, ContractId, PiecrustError, Session,
TRANSFER_CONTRACT,
};

use dusk_bytes::Serializable;
Expand Down Expand Up @@ -143,7 +144,7 @@ pub fn prover_verifier(input_notes: usize) -> (Prover, Verifier) {
pub fn execute(
session: &mut Session,
tx: impl Into<Transaction>,
) -> Result<u64, PiecrustError> {
) -> Result<CallReceipt<Result<Vec<u8>, ContractError>>, PiecrustError> {
let tx = tx.into();

let mut receipt = session.call::<_, Result<Vec<u8>, ContractError>>(
Expand All @@ -169,7 +170,20 @@ pub fn execute(

receipt.events.extend(refund_receipt.events);

Ok(receipt.gas_spent)
Ok(receipt)
}

pub fn owned_notes_value<'a, I: IntoIterator<Item = &'a Note>>(
vk: ViewKey,
notes: I,
) -> u64 {
notes.into_iter().fold(0, |acc, note| {
acc + if vk.owns(note.stealth_address()) {
note.value(Some(&vk)).unwrap()
} else {
0
}
})
}

/// Returns vector of notes owned by a given view key.
Expand All @@ -183,21 +197,17 @@ pub fn filter_notes_owned_by<I: IntoIterator<Item = Note>>(
}

pub fn create_moonlight_transaction(
session: &mut Session,
from_sk: &BlsSecretKey,
to: Option<BlsPublicKey>,
value: u64,
deposit: u64,
gas_limit: u64,
gas_price: u64,
nonce: u64,
exec: Option<impl Into<ContractExec>>,
) -> MoonlightTransaction {
let from = BlsPublicKey::from(from_sk);

let account =
account(session, &from).expect("Getting the account should work");
let nonce = account.nonce + 1;

let payload = MoonlightPayload {
from,
to,
Expand Down
Loading

0 comments on commit 2a1822b

Please sign in to comment.