Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native token contract wrapper interface #338

Merged
merged 8 commits into from
Sep 13, 2022
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