Skip to content

Commit

Permalink
Add utils for converting from/to contract/account ids. (#876)
Browse files Browse the repository at this point in the history
### What

Add utils for converting from/to contract/account ids.

### Why

This covers some relatively rare, but seemingly useful Address use
cases, such as transferring tokens to the newly deployed contracts or
supporting cross-chain bridges.

### Known limitations

N/A
  • Loading branch information
dmkozh authored Feb 27, 2023
1 parent b7cc604 commit 4fdb7e9
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 26 deletions.
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ soroban-token-spec = { version = "0.6.0", path = "soroban-token-spec" }
[workspace.dependencies.soroban-env-common]
version = "0.0.14"
git = "https://github.com/stellar/rs-soroban-env"
rev = "dc7f13554c9c318fbae7a5ffb043ca43156f79d7"
rev = "5695440da452837555d8f7f259cc33341fdf07b0"

[workspace.dependencies.soroban-env-guest]
version = "0.0.14"
git = "https://github.com/stellar/rs-soroban-env"
rev = "dc7f13554c9c318fbae7a5ffb043ca43156f79d7"
rev = "5695440da452837555d8f7f259cc33341fdf07b0"

[workspace.dependencies.soroban-env-host]
version = "0.0.14"
git = "https://github.com/stellar/rs-soroban-env"
rev = "dc7f13554c9c318fbae7a5ffb043ca43156f79d7"
rev = "5695440da452837555d8f7f259cc33341fdf07b0"

[workspace.dependencies.stellar-strkey]
version = "0.0.7"
Expand Down
84 changes: 73 additions & 11 deletions soroban-sdk/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ use core::{cmp::Ordering, fmt::Debug};
use super::{
env::internal::{Env as _, EnvBase as _},
xdr::ScObjectType,
ConversionError, Env, Object, RawVal, TryFromVal,
BytesN, ConversionError, Env, Object, RawVal, TryFromVal, TryIntoVal,
};

#[cfg(not(target_family = "wasm"))]
use crate::env::internal::xdr::ScVal;
use crate::{unwrap::UnwrapInfallible, Vec};
use crate::{
unwrap::{UnwrapInfallible, UnwrapOptimized},
Vec,
};

/// Address is a universal opaque identifier to use in contracts.
///
Expand Down Expand Up @@ -144,7 +147,6 @@ impl TryFrom<Address> for ScVal {
impl TryFromVal<Env, ScVal> for Address {
type Error = ConversionError;
fn try_from_val(env: &Env, val: &ScVal) -> Result<Self, Self::Error> {
use soroban_env_host::TryIntoVal;
<_ as TryFromVal<_, Object>>::try_from_val(
env,
&val.try_into_val(env).map_err(|_| ConversionError)?,
Expand Down Expand Up @@ -197,6 +199,73 @@ impl Address {
self.env.require_auth(&self);
}

/// Creates an `Address` corresponding to the provided contract identifier.
///
/// Prefer using the `Address` directly as input or output argument. Only
/// use this in special cases, for example to get an Address of a freshly
/// deployed contract.
pub fn from_contract_id(env: &Env, contract_id: &BytesN<32>) -> Self {
unsafe {
Self::unchecked_new(
env.clone(),
env.contract_id_to_address(contract_id.to_object())
.unwrap_optimized(),
)
}
}

/// Creates an `Address` corresponding to the provided Stellar account
/// 32-byte identifier (public key).
///
/// Prefer using the `Address` directly as input or output argument. Only
/// use this in special cases, like for cross-chain interoperability.
pub fn from_account_id(env: &Env, account_pk: &BytesN<32>) -> Self {
unsafe {
Self::unchecked_new(
env.clone(),
env.account_public_key_to_address(account_pk.to_object())
.unwrap_optimized(),
)
}
}

/// Returns 32-byte contract identifier corresponding to this `Address`.
///
/// Returns `None` when this `Address` does not belong to a contract.
///
/// Avoid using the returned contract identifier for authorization purposes
/// and prefer using `Address` directly whenever possible. This is only
/// useful in special cases, for example, to be able to invoke a contract
/// given its `Address`.
pub fn contract_id(&self) -> Option<BytesN<32>> {
let rv = self.env.address_to_contract_id(self.obj).unwrap_optimized();
if let Ok(()) = rv.try_into_val(&self.env) {
None
} else {
Some(rv.try_into_val(&self.env).unwrap_optimized())
}
}

/// Returns 32-byte Stellar account identifier (public key) corresponding
/// to this `Address`.
///
/// Returns `None` when this `Address` does not belong to an account.
///
/// Avoid using the returned account identifier for authorization purposes
/// and prefer using `Address` directly whenever possible. This is only
/// useful in special cases, like for cross-chain interoperability.
pub fn account_id(&self) -> Option<BytesN<32>> {
let rv = self
.env
.address_to_account_public_key(self.obj)
.unwrap_optimized();
if let Ok(()) = rv.try_into_val(&self.env) {
None
} else {
Some(rv.try_into_val(&self.env).unwrap_optimized())
}
}

#[inline(always)]
pub(crate) unsafe fn unchecked_new(env: Env, obj: Object) -> Self {
Self { env, obj }
Expand Down Expand Up @@ -227,17 +296,10 @@ impl Address {
#[cfg(any(test, feature = "testutils"))]
use crate::env::xdr::{Hash, ScAddress, ScObject};
#[cfg(any(test, feature = "testutils"))]
use crate::{testutils::random, BytesN};
use crate::testutils::random;
#[cfg(any(test, feature = "testutils"))]
#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
impl crate::testutils::Address for Address {
fn from_contract_id(env: &Env, contract_id: &BytesN<32>) -> Self {
let sc_addr = ScVal::Object(Some(ScObject::Address(ScAddress::Contract(Hash(
contract_id.to_array(),
)))));
Self::try_from_val(env, &sc_addr).unwrap()
}

fn random(env: &Env) -> Self {
let sc_addr = ScVal::Object(Some(ScObject::Address(ScAddress::Contract(Hash(random())))));
Self::try_from_val(env, &sc_addr).unwrap()
Expand Down
1 change: 1 addition & 0 deletions soroban-sdk/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![cfg(test)]

mod address;
mod budget;
mod contract_add_i32;
mod contract_assert;
Expand Down
23 changes: 23 additions & 0 deletions soroban-sdk/src/tests/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crate::{Address, BytesN, Env};

#[test]
fn test_account_address_conversions() {
let env = Env::default();
let account_address = Address::from_account_id(&env, &BytesN::from_array(&env, &[222u8; 32]));
assert_eq!(
account_address.account_id(),
Some(BytesN::from_array(&env, &[222u8; 32]))
);
assert_eq!(account_address.contract_id(), None);
}

#[test]
fn test_contract_address_conversions() {
let env = Env::default();
let contract_address = Address::from_contract_id(&env, &BytesN::from_array(&env, &[111u8; 32]));
assert_eq!(
contract_address.contract_id(),
Some(BytesN::from_array(&env, &[111u8; 32]))
);
assert_eq!(contract_address.account_id(), None);
}
2 changes: 1 addition & 1 deletion soroban-sdk/src/tests/contractfile_with_sha256.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate as soroban_sdk;
pub const WASM: &[u8] = soroban_sdk::contractfile!(
file = "../target/wasm32-unknown-unknown/release/test_add_u64.wasm",
sha256 = "8defec8d424eb76db7e1d66a7d19e31ff34afae5dafdf5fca9ec59ed53ab9a63",
sha256 = "50ef498f6a4ed5f793f4b0cacc3283eec3d134f88f4e597936efa558c5ba5093",
);

#[test]
Expand Down
2 changes: 1 addition & 1 deletion soroban-sdk/src/tests/contractimport_with_sha256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod addcontract {
use crate as soroban_sdk;
soroban_sdk::contractimport!(
file = "../target/wasm32-unknown-unknown/release/test_add_u64.wasm",
sha256 = "8defec8d424eb76db7e1d66a7d19e31ff34afae5dafdf5fca9ec59ed53ab9a63",
sha256 = "50ef498f6a4ed5f793f4b0cacc3283eec3d134f88f4e597936efa558c5ba5093",
);
}

Expand Down
5 changes: 0 additions & 5 deletions soroban-sdk/src/testutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,6 @@ pub(crate) fn random<const N: usize>() -> [u8; N] {
}

pub trait Address {
/// Build an address from a contract identifier.
///
/// This is useful to create an Address of the registered contract.
fn from_contract_id(env: &Env, contract_id: &crate::BytesN<32>) -> crate::Address;

/// Create a random Address.
///
/// Implementation note: this always builds the contract addresses now. This
Expand Down

0 comments on commit 4fdb7e9

Please sign in to comment.