Skip to content

Commit

Permalink
Native token contract wrapper interface (#338)
Browse files Browse the repository at this point in the history
* Implement transfer_account_balance and transfer_trustline_balance

* Add push and append to Bytes

* Change metadata for token to support classic assets

* Implement to_smart and to_classic

* Add frame check to ensure only token can transfer classic balance

* Add missing auth check for transfer_trustline_balance

* Fixes from merge
  • Loading branch information
jonjove authored Sep 13, 2022
1 parent 34b8108 commit aac3024
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 56 deletions.
115 changes: 115 additions & 0 deletions soroban-env-host/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ pub struct LedgerInfo {
pub sequence_number: u32,
pub timestamp: u64,
pub network_passphrase: Vec<u8>,
pub base_reserve: u32,
}

#[derive(Clone, Default)]
Expand Down Expand Up @@ -809,6 +810,120 @@ impl Host {
self.record_contract_event(ContractEventType::System, topics, data)?;
Ok(Status::OK.into())
}

pub(crate) fn transfer_account_balance(
&self,
account_id: Object,
amount: i64,
) -> Result<(), HostError> {
use xdr::{AccountEntryExt, AccountEntryExtensionV1Ext, LedgerKeyAccount};

self.with_current_frame(|frame| match frame {
Frame::Token(id, _) => Ok(()),
_ => Err(self.err_general("only native token can transfer classic balance")),
})?;

let lk = LedgerKey::Account(LedgerKeyAccount {
account_id: AccountId(PublicKey::PublicKeyTypeEd25519(self.to_u256(account_id)?)),
});
self.visit_storage(|storage| {
let mut le = storage.get(&lk)?;
let ae = match &mut le.data {
LedgerEntryData::Account(ae) => Ok(ae),
_ => Err(self.err_general("not account")),
}?;
if ae.balance < 0 {
return Err(self.err_general("balance is negative"));
}

let base_reserve = self.with_ledger_info(|li| Ok(li.base_reserve))? as i64;
let (min_balance, max_balance) = if let AccountEntryExt::V1(ext1) = &ae.ext {
let net_entries = if let AccountEntryExtensionV1Ext::V2(ext2) = &ext1.ext {
2i64 + (ae.num_sub_entries as i64) + (ext2.num_sponsored as i64)
- (ext2.num_sponsoring as i64)
} else {
2i64 + ae.num_sub_entries as i64
};
let min_balance = net_entries * base_reserve + ext1.liabilities.selling;
let max_balance = i64::MAX - ext1.liabilities.buying;
(min_balance, max_balance)
} else {
let net_entries = 2i64 + (ae.num_sub_entries as i64);
let min_balance = net_entries * base_reserve;
let max_balance = i64::MAX;
(min_balance, max_balance)
};

let new_balance = if amount <= 0 {
ae.balance + amount
} else if ae.balance <= i64::MAX - amount {
ae.balance + amount
} else {
return Err(self.err_general("balance overflowed"));
};
if new_balance >= min_balance && new_balance <= max_balance {
ae.balance = new_balance;
storage.put(&lk, &le)
} else {
Err(self.err_general("invalid balance"))
}
})
}

pub(crate) fn transfer_trustline_balance(
&self,
account_id: Object,
asset_code: Object,
issuer: Object,
amount: i64,
) -> Result<(), HostError> {
use xdr::{TrustLineEntryExt, TrustLineFlags};

self.with_current_frame(|frame| match frame {
Frame::Token(id, _) => Ok(()),
_ => Err(self.err_general("only native token can transfer classic balance")),
})?;

let lk = self.to_trustline_key(account_id, asset_code, issuer)?;
self.visit_storage(|storage| {
let mut le = storage.get(&lk)?;
let tl = match &mut le.data {
LedgerEntryData::Trustline(tl) => Ok(tl),
_ => Err(self.err_general("not trustline")),
}?;
if tl.balance < 0 {
return Err(self.err_general("balance is negative"));
}
if tl.flags & (TrustLineFlags::AuthorizedFlag as u32) == 0 {
return Err(self.err_general("not authorized"));
}

let base_reserve = self.with_ledger_info(|li| Ok(li.base_reserve))? as i64;
let (min_balance, max_balance) = if let TrustLineEntryExt::V1(ext1) = &tl.ext {
let min_balance = ext1.liabilities.selling;
let max_balance = i64::MAX - ext1.liabilities.buying;
(min_balance, max_balance)
} else {
let min_balance = 0;
let max_balance = i64::MAX;
(min_balance, max_balance)
};

let new_balance = if amount <= 0 {
tl.balance + amount
} else if tl.balance <= i64::MAX - amount {
tl.balance + amount
} else {
return Err(self.err_general("balance overflowed"));
};
if new_balance >= min_balance && new_balance <= max_balance {
tl.balance = new_balance;
storage.put(&lk, &le)
} else {
Err(self.err_general("invalid balance"))
}
})
}
}

// Notes on metering: these are called from the guest and thus charged on the VM instructions.
Expand Down
37 changes: 32 additions & 5 deletions soroban-env-host/src/host/data_helper.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use crate::budget::CostType;
use crate::xdr::{
ContractDataEntry, HashIdPreimage, HashIdPreimageContractId, HashIdPreimageEd25519ContractId,
LedgerEntry, LedgerEntryData, LedgerEntryExt, LedgerKey, LedgerKeyAccount,
LedgerKeyContractData, ScContractCode, ScHostStorageErrorCode, ScHostValErrorCode, ScObject,
ScStatic, ScVal,
AccountEntry, AccountId, ContractDataEntry, Hash, HashIdPreimage, HashIdPreimageContractId,
HashIdPreimageEd25519ContractId, LedgerEntry, LedgerEntryData, LedgerEntryExt, LedgerKey,
LedgerKeyAccount, LedgerKeyContractData, LedgerKeyTrustLine, PublicKey, ScContractCode,
ScHostStorageErrorCode, ScHostValErrorCode, ScObject, ScStatic, ScVal, Uint256, WriteXdr,
};
use crate::{Host, HostError, Object};
use soroban_env_common::xdr::{AccountEntry, AccountId, Hash, PublicKey, Uint256, WriteXdr};

impl Host {
// Notes on metering: free
Expand Down Expand Up @@ -110,4 +109,32 @@ impl Host {
});
self.visit_storage(|storage| storage.has(&acc))
}

pub fn to_trustline_key(
&self,
account_id: Object,
asset_code: Object,
issuer: Object,
) -> Result<LedgerKey, HostError> {
use crate::xdr::{AlphaNum12, AlphaNum4, AssetCode12, AssetCode4, TrustLineAsset};
let asset = self.visit_obj(asset_code, |b: &Vec<u8>| {
if b.len() > 0 && b.len() <= 4 {
Ok(TrustLineAsset::CreditAlphanum4(AlphaNum4 {
asset_code: AssetCode4(b.as_slice().try_into().unwrap()),
issuer: AccountId(PublicKey::PublicKeyTypeEd25519(self.to_u256(issuer)?)),
}))
} else if b.len() > 0 && b.len() <= 12 {
Ok(TrustLineAsset::CreditAlphanum12(AlphaNum12 {
asset_code: AssetCode12(b.as_slice().try_into().unwrap()),
issuer: AccountId(PublicKey::PublicKeyTypeEd25519(self.to_u256(issuer)?)),
}))
} else {
Err(self.err_general("invalid asset code"))
}
})?;
Ok(LedgerKey::Trustline(LedgerKeyTrustLine {
account_id: AccountId(PublicKey::PublicKeyTypeEd25519(self.to_u256(account_id)?)),
asset,
}))
}
}
31 changes: 29 additions & 2 deletions soroban-env-host/src/native_contract/base_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,35 @@ impl TryIntoVal<Host, RawVal> for Bytes {
}

impl From<Bytes> for Object {
fn from(vec: Bytes) -> Self {
vec.0.val
fn from(b: Bytes) -> Self {
b.0.val
}
}

impl<const N: u32> From<BytesN<N>> for Bytes {
fn from(b: BytesN<N>) -> Self {
Self(b.0)
}
}

impl Bytes {
pub fn push(&mut self, x: u8) -> Result<(), HostError> {
let x32: u32 = x.into();
self.0 = self
.0
.env
.bytes_push(self.0.val, x32.into())?
.in_env(&self.0.env);
Ok(())
}

pub fn append(&mut self, other: Bytes) -> Result<(), HostError> {
self.0 = self
.0
.env
.bytes_append(self.0.val, other.0.val)?
.in_env(&self.0.env);
Ok(())
}
}

Expand Down
25 changes: 23 additions & 2 deletions soroban-env-host/src/native_contract/token/balance.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::host::Host;
use crate::native_contract::base_types::BigInt;
use crate::native_contract::base_types::{BigInt, BytesN};
use crate::native_contract::token::error::Error;
use crate::native_contract::token::public_types::Identifier;
use crate::native_contract::token::metadata::read_metadata;
use crate::native_contract::token::public_types::{Identifier, Metadata};
use crate::native_contract::token::storage_types::DataKey;
use core::cmp::Ordering;
use soroban_env_common::{CheckedEnv, TryIntoVal};
Expand Down Expand Up @@ -57,3 +58,23 @@ pub fn write_state(e: &Host, id: Identifier, is_frozen: bool) -> Result<(), Erro
e.put_contract_data(key.try_into_val(e)?, is_frozen.into())?;
Ok(())
}

pub fn transfer_classic_balance(e: &Host, to_key: BytesN<32>, amount: i64) -> Result<(), Error> {
match read_metadata(e)? {
Metadata::Token(_) => return Err(Error::ContractError),
Metadata::Native => e.transfer_account_balance(to_key.into(), amount)?,
Metadata::AlphaNum4(asset) => e.transfer_trustline_balance(
to_key.into(),
asset.asset_code.into(),
asset.issuer.into(),
amount,
)?,
Metadata::AlphaNum12(asset) => e.transfer_trustline_balance(
to_key.into(),
asset.asset_code.into(),
asset.issuer.into(),
amount,
)?,
};
Ok(())
}
75 changes: 54 additions & 21 deletions soroban-env-host/src/native_contract/token/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,20 @@ use crate::native_contract::base_types::{BigInt, Bytes, Vec};
use crate::native_contract::token::admin::{check_admin, has_administrator, write_administrator};
use crate::native_contract::token::allowance::{read_allowance, spend_allowance, write_allowance};
use crate::native_contract::token::balance::{
read_balance, read_state, receive_balance, spend_balance, write_state,
read_balance, read_state, receive_balance, spend_balance, transfer_classic_balance, write_state,
};
use crate::native_contract::token::cryptography::check_auth;
use crate::native_contract::token::error::Error;
use crate::native_contract::token::metadata::{
read_decimal, read_name, read_symbol, write_decimal, write_name, write_symbol,
read_decimal, read_name, read_symbol, write_metadata,
};
use crate::native_contract::token::nonce::read_nonce;
use crate::native_contract::token::public_types::{Identifier, Signature};
use crate::native_contract::token::public_types::{Identifier, Metadata, Signature};
use soroban_env_common::{Symbol, TryIntoVal};
use soroban_native_sdk_macros::contractimpl;

pub trait TokenTrait {
fn initialize(
e: &Host,
admin: Identifier,
decimal: u32,
name: Bytes,
symbol: Bytes,
) -> Result<(), Error>;
fn initialize(e: &Host, admin: Identifier, metadata: Metadata) -> Result<(), Error>;

fn nonce(e: &Host, id: Identifier) -> Result<BigInt, Error>;

Expand Down Expand Up @@ -89,27 +83,22 @@ pub trait TokenTrait {
fn name(e: &Host) -> Result<Bytes, Error>;

fn symbol(e: &Host) -> Result<Bytes, Error>;

fn to_smart(e: &Host, id: Signature, nonce: BigInt, amount: i64) -> Result<(), Error>;

fn to_classic(e: &Host, id: Signature, nonce: BigInt, amount: i64) -> Result<(), Error>;
}

pub struct Token;

#[contractimpl]
impl TokenTrait for Token {
fn initialize(
e: &Host,
admin: Identifier,
decimal: u32,
name: Bytes,
symbol: Bytes,
) -> Result<(), Error> {
fn initialize(e: &Host, admin: Identifier, metadata: Metadata) -> Result<(), Error> {
if has_administrator(&e)? {
return Err(Error::ContractError);
}
write_administrator(&e, admin)?;

write_decimal(&e, u8::try_from(decimal).map_err(|_| Error::ContractError)?)?;
write_name(&e, name)?;
write_symbol(&e, symbol)?;
write_metadata(&e, metadata)?;
Ok(())
}

Expand Down Expand Up @@ -273,4 +262,48 @@ impl TokenTrait for Token {
fn symbol(e: &Host) -> Result<Bytes, Error> {
read_symbol(&e)
}

fn to_smart(e: &Host, id: Signature, nonce: BigInt, amount: i64) -> Result<(), Error> {
if amount < 0 {
return Err(Error::ContractError);
}

let id_key = match &id {
Signature::Account(acc) => Ok(acc.account_id.clone()),
_ => Err(Error::ContractError),
}?;
let mut args = Vec::new(e)?;
args.push(amount.clone())?;
check_auth(&e, id, nonce, Symbol::from_str("to_smart"), args)?;

transfer_classic_balance(e, id_key.clone(), amount)?;
receive_balance(
&e,
Identifier::Account(id_key),
BigInt::from_u64(&e, amount.try_into().map_err(|_| Error::ContractError)?)?,
)?;
Ok(())
}

fn to_classic(e: &Host, id: Signature, nonce: BigInt, amount: i64) -> Result<(), Error> {
if amount < 0 {
return Err(Error::ContractError);
}

let id_key = match &id {
Signature::Account(acc) => Ok(acc.account_id.clone()),
_ => Err(Error::ContractError),
}?;
let mut args = Vec::new(e)?;
args.push(amount.clone())?;
check_auth(&e, id, nonce, Symbol::from_str("to_classic"), args)?;

transfer_classic_balance(e, id_key.clone(), -amount)?;
spend_balance(
&e,
Identifier::Account(id_key),
BigInt::from_u64(&e, amount.try_into().map_err(|_| Error::ContractError)?)?,
)?;
Ok(())
}
}
Loading

0 comments on commit aac3024

Please sign in to comment.