Skip to content

Commit

Permalink
Fix asset-code rendering in native contract.
Browse files Browse the repository at this point in the history
  • Loading branch information
graydon committed Sep 6, 2023
1 parent 243a362 commit c046b37
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 34 deletions.
24 changes: 10 additions & 14 deletions soroban-env-host/src/native_contract/base_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::host::metered_clone::MeteredClone;
use crate::host::{Host, HostError};

use core::cmp::Ordering;
use soroban_env_common::xdr::{AccountId, ScAddress};
use soroban_env_common::xdr::{AccountId, ScAddress, ScErrorCode, ScErrorType};
use soroban_env_common::{
AddressObject, BytesObject, Compare, ConversionError, Env, EnvBase, MapObject, StringObject,
TryFromVal, Val, VecObject,
Expand Down Expand Up @@ -57,22 +57,18 @@ impl From<String> for StringObject {
}
}

// TODO: check metering
impl String {
pub fn copy_to_rust_string(&self, env: &Host) -> Result<std::string::String, HostError> {
let len: u32 = env
.string_len(self.object)
.map_err(|_| ConversionError)?
.into();
let len = len as usize;
let mut vec = std::vec![0; len];
env.string_copy_to_slice(self.object, Val::U32_ZERO, &mut vec)
.map_err(|_| ConversionError)?;
std::string::String::from_utf8(vec).map_err(|_| ConversionError.into())
}

pub fn to_array<const N: usize>(&self) -> Result<[u8; N], HostError> {
let mut slice = [0_u8; N];
let len = self.host.string_len(self.object)?;
if u32::from(len) as usize != N {
return Err(self.host.err(
ScErrorType::Value,
ScErrorCode::UnexpectedSize,
"String had unexpected size",
&[len.to_val()],
));
}
self.host
.string_copy_to_slice(self.object, Val::U32_ZERO, &mut slice)?;
Ok(slice)
Expand Down
160 changes: 140 additions & 20 deletions soroban-env-host/src/native_contract/token/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use std::fmt::Write;

use soroban_native_sdk_macros::contracttype;
use stellar_strkey::ed25519;

use crate::{host::Host, HostError};
use soroban_env_common::{Env, EnvBase, StorageType, SymbolSmall, TryFromVal, TryIntoVal};
use crate::{host::Host, native_contract::base_types::BytesN, HostError};
use soroban_env_common::{
ConversionError, Env, EnvBase, StorageType, SymbolSmall, TryFromVal, TryIntoVal,
};

use crate::native_contract::base_types::String;

Expand All @@ -20,33 +24,149 @@ pub struct TokenMetadata {

pub const DECIMAL: u32 = 7;

// This does a specific and fairly unique escaping transformation as defined
// in TxRep / SEP-0011.
fn render_sep0011_asset_code(
buf: &[u8],
out: &mut std::string::String,
) -> Result<(), ConversionError> {
if buf.len() != 4 && buf.len() != 12 {
return Err(ConversionError);
}
for (i, x) in buf.iter().enumerate() {
match *x {
// When dealing with a 4-byte asset code we stop at the first NUL.
0 if buf.len() == 4 => break,
// When dealing with a 12-byte asset code we continue escaping NULs
// as \x00 until past the 5th byte, so that the result is
// unambiguously different than a 4-byte code.
0 if buf.len() == 12 && i > 4 => break,
b':' | b'\\' | 0..=0x20 | 0x7f..=0xff => {
write!(out, r"\x{:02x}", x).map_err(|_| ConversionError)?
}
_ => out.push(*x as char),
}
}
Ok(())
}

#[test]
fn test_render_sep0011_asset_code() {
fn check_pair(a: &[u8], b: &str) {
let mut out = std::string::String::new();
render_sep0011_asset_code(a, &mut out).unwrap();
assert_eq!(out, b);
}
// 4 byte codes
check_pair(&[0, 0, 0, 0], r"");
check_pair(&[0, b'X', b'L', b'M'], r"");
check_pair(&[b'X', 0, 0, 0], r"X");
check_pair(&[b'X', b'L', 0, 0], r"XL");
check_pair(&[b'X', b'L', b'M', 0], r"XLM");
check_pair(&[b'y', b'X', b'L', b'M'], r"yXLM");
check_pair(&[1, b'X', b'L', b'M'], r"\x01XLM");
check_pair(&[b'X', 1, b'L', b'M'], r"X\x01LM");
check_pair(&[b'X', b':', b'L', b'M'], r"X\x3aLM");
check_pair(&[b'X', b'\\', b'L', b'M'], r"X\x5cLM");
check_pair(&[b'1', b'!', b'/', b'0'], r"1!/0");

// 12 byte codes
check_pair(
&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
r"\x00\x00\x00\x00\x00",
);
check_pair(
&[0, b'X', b'L', b'M', 0, 0, 0, 0, 0, 0, 0, 0],
r"\x00XLM\x00",
);
check_pair(
&[b'X', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
r"X\x00\x00\x00\x00",
);
check_pair(
&[b'X', b'L', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
r"XL\x00\x00\x00",
);
check_pair(
&[b'X', b'L', b'M', 0, 0, 0, 0, 0, 0, 0, 0, 0],
r"XLM\x00\x00",
);
check_pair(
&[b'y', b'X', b'L', b'M', 0, 0, 0, 0, 0, 0, 0, 0],
r"yXLM\x00",
);
check_pair(
&[1, b'X', b'L', b'M', 0, 0, 0, 0, 0, 0, 0, 0],
r"\x01XLM\x00",
);
check_pair(
&[b'X', 1, b'L', b'M', 0, 0, 0, 0, 0, 0, 0, 0],
r"X\x01LM\x00",
);
check_pair(
&[b'X', b':', b'L', b'M', 0, 0, 0, 0, 0, 0, 0, 0],
r"X\x3aLM\x00",
);
check_pair(
&[b'X', b'\\', b'L', b'M', 0, 0, 0, 0, 0, 0, 0, 0],
r"X\x5cLM\x00",
);
check_pair(
&[b'X', b'L', b'M', b'L', b'X', 0, 0, 0, 0, 0, 0, 0],
r"XLMLX",
);
check_pair(
&[b'X', b'L', b'M', b'L', b'X', b'A', 0, 0, 0, 0, 0, 0],
r"XLMLXA",
);
check_pair(
&[
b'1', b'!', b'/', b'0', b'a', b'<', b'N', b'!', b'[', b'K', b'z', b'^',
],
r"1!/0a<N![Kz^",
);
}

fn render_sep0011_asset<const N: usize>(
e: &Host,
symbol: String,
issuer: BytesN<32>,
) -> Result<(String, String), HostError> {
let symbuf = symbol.to_array::<N>()?;
let strkey_len = 56;

// Biggest resulting string has each byte escaped to 4 bytes.
let capacity = symbuf.len() * 4 + 1 + strkey_len;

// We also have to charge for strkey_len again since PublicKey::to_string does
// a std::string::String allocation of its own.
let charge = capacity + strkey_len;
e.charge_budget(
crate::xdr::ContractCostType::HostMemAlloc,
Some(charge as u64),
)?;

let mut s: std::string::String = std::string::String::with_capacity(capacity);
render_sep0011_asset_code(&symbuf, &mut s)?;
s.push(':');
s.push_str(&ed25519::PublicKey(issuer.to_array()?).to_string());
Ok((
String::try_from_val(e, &e.string_new_from_slice(s.as_str())?)?,
symbol,
))
}

pub fn set_metadata(e: &Host) -> Result<(), HostError> {
let name_and_symbol: (String, String) = match read_asset_info(e)? {
AssetInfo::Native => {
let n = String::try_from_val(e, &e.string_new_from_slice("native")?)?;
(n.clone(), n)
}
AssetInfo::AlphaNum4(asset) => {
let symbol: String = asset.asset_code;
let mut name = symbol.copy_to_rust_string(e)?;
name.push(':');
let k = ed25519::PublicKey(asset.issuer.to_array()?);
name.push_str(k.to_string().as_str());
(
String::try_from_val(e, &e.string_new_from_slice(name.as_str())?)?,
symbol,
)
render_sep0011_asset::<4>(e, asset.asset_code, asset.issuer)?
}
AssetInfo::AlphaNum12(asset) => {
let symbol: String = asset.asset_code;
let mut name = symbol.copy_to_rust_string(e)?;
name.push(':');
let k = ed25519::PublicKey(asset.issuer.to_array()?);
name.push_str(k.to_string().as_str());
(
String::try_from_val(e, &e.string_new_from_slice(name.as_str())?)?,
symbol,
)
render_sep0011_asset::<12>(e, asset.asset_code, asset.issuer)?
}
};

Expand Down

0 comments on commit c046b37

Please sign in to comment.