Skip to content

Commit f42f6bf

Browse files
leighmccullochElliotFriendclaude
authored
Fix outdated Address and token APIs in EVM deployment migration guide (#2689)
* Fix outdated Address and token APIs in EVM deployment migration guide ### What Replace the removed `Address::random` call with `Address::generate`, update the quoted token-interface `approve` parameter name, bump the pinned soroban-sdk version, and point the example clone at a current ref. ### Why `Address::random` no longer exists in the SDK and the quoted parameter name was renamed, so a reader following this migration guide would hit compile errors and copy an incorrect interface. * Align vault guide with the current soroban-examples token contract Pointing the clone at `main` (previous commit) changes the token contract's ABI, so the surrounding guide needed to follow: - The token sets its admin and metadata in `__constructor`; there is no `initialize` method. `create_contract` now passes those through `deploy_v2`, the vault's `initialize` builds them with `String::from_str`, and the quoted interface declares `__constructor` as an inherent function on the contract type rather than a trait method (it cannot live in a trait impl, and needs no "already initialized" guard). - The `initialize_token.sh` script is gone. Its arguments move onto `stellar contract deploy` after the `--`, so the token is deployed and initialized in one transaction. - `transfer`'s destination is now a `MuxedAddress`, resolved with `.address()` before touching balances. Call sites are unchanged: the generated client takes `impl Into<MuxedAddress>`, and `MuxedAddress` implements `From<&Address>`. - `stellar contract install` is deprecated in favour of `contract upload`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Elliot <elliot@stellar.org> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 5af27fb commit f42f6bf

1 file changed

Lines changed: 66 additions & 68 deletions

File tree

docs/learn/migrate/evm/smart-contract-deployment.mdx

Lines changed: 66 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,10 @@ If you haven't already setup up the dev environment for Soroban, you can get sta
188188

189189
This project requires using the `soroban_token_contract.wasm` file which you will need to import manually.
190190

191-
First, you will need to clone the `v22.0.1` tag of `soroban-examples` repository:
191+
First, you will need to clone the `main` branch of `soroban-examples` repository:
192192

193193
```bash
194-
git clone -b v22.0.1 https://github.com/stellar/soroban-examples
194+
git clone -b main https://github.com/stellar/soroban-examples
195195
```
196196

197197
Then, navigate to the `soroban-examples/token` directory
@@ -254,11 +254,11 @@ publish = false
254254
crate-type = ["cdylib"]
255255

256256
[dependencies]
257-
soroban-sdk = { version = "26" }
257+
soroban-sdk = { version = "27" }
258258
num-integer = { version = "0.1.45", default-features = false, features = ["i128"] }
259259

260260
[dev-dependencies]
261-
soroban-sdk = { version = "26", features = ["testutils"] }
261+
soroban-sdk = { version = "27", features = ["testutils"] }
262262

263263
[profile.release]
264264
opt-level = "z"
@@ -281,7 +281,7 @@ In this project we will need to create 3 files:
281281
- `src/test.rs` - This is where we will write our tests.
282282
- `src/token.rs` - This is file inherits the token contact that we imported earlier. It's also where we will write our token creation logic.
283283

284-
To interact with the token contract, we'll use a built in interface that you can find in the `token_interface.rs` tab. This interface includes the `initialize` and `mint` functions that we will use to create and mint tokens for us to use in our vault contract. If you want to see the full code of the token contract, you can check it out [here](https://github.com/stellar/soroban-examples/tree/main/token/src).
284+
To interact with the token contract, we'll use a built in interface that you can find in the `token_interface.rs` tab. The token sets its admin and metadata in a `__constructor` function, which the host runs as part of the deployment itself, and exposes a `mint` function that we will use to mint tokens for our vault contract. Because the constructor runs at deployment, there is no separate initialization call to make — or to front-run. If you want to see the full code of the token contract, you can check it out [here](https://github.com/stellar/soroban-examples/tree/main/token/src).
285285

286286
<Tabs>
287287
<TabItem value="lib.rs" label="src/lib.rs">
@@ -293,7 +293,7 @@ mod test;
293293
mod token;
294294

295295
use soroban_sdk::{
296-
contract, contractimpl, contractmeta, Address, BytesN, ConversionError, Env, IntoVal,
296+
contract, contractimpl, contractmeta, Address, BytesN, ConversionError, Env, String,
297297
TryFromVal, Val,
298298
};
299299
use token::create_contract;
@@ -407,12 +407,16 @@ struct Vault;
407407
#[contractimpl]
408408
impl VaultTrait for Vault {
409409
fn initialize(e: Env, token_wasm_hash: BytesN<32>, token: Address) {
410-
let share_contract_id = create_contract(&e, token_wasm_hash, &token);
411-
token::Client::new(&e, &share_contract_id).initialize(
412-
&e.current_contract_address(),
413-
&7u32,
414-
&"Vault Share Token".into_val(&e),
415-
&"VST".into_val(&e),
410+
// The share token's admin and metadata are passed straight to its
411+
// `__constructor`, so it is deployed and initialized in a single step.
412+
let share_contract_id = create_contract(
413+
&e,
414+
token_wasm_hash,
415+
&token,
416+
e.current_contract_address(),
417+
7u32,
418+
String::from_str(&e, "Vault Share Token"),
419+
String::from_str(&e, "VST"),
416420
);
417421

418422
put_token(&e, token);
@@ -507,11 +511,11 @@ fn test() {
507511
let e = Env::default();
508512
e.mock_all_auths();
509513

510-
let admin1 = Address::random(&e);
514+
let admin1 = Address::generate(&e);
511515

512516
let token = create_token_contract(&e, &admin1);
513517

514-
let user1 = Address::random(&e);
518+
let user1 = Address::generate(&e);
515519

516520
let vault = create_vault_contract(&e, &install_token_wasm(&e), &token.address);
517521

@@ -588,17 +592,27 @@ fn test() {
588592

589593
```rust
590594
#![allow(unused)]
591-
use soroban_sdk::{xdr::ToXdr, Address, Bytes, BytesN, Env};
595+
use soroban_sdk::{xdr::ToXdr, Address, Bytes, BytesN, Env, String};
592596

593597
soroban_sdk::contractimport!(file = "./soroban_token_contract.wasm");
594598

595-
pub fn create_contract(e: &Env, token_wasm_hash: BytesN<32>, token: &Address) -> Address {
599+
pub fn create_contract(
600+
e: &Env,
601+
token_wasm_hash: BytesN<32>,
602+
token: &Address,
603+
admin: Address,
604+
decimal: u32,
605+
name: String,
606+
symbol: String,
607+
) -> Address {
596608
let mut salt = Bytes::new(e);
597609
salt.append(&token.to_xdr(e));
598610
let salt = e.crypto().sha256(&salt);
599611
e.deployer()
600612
.with_current_contract(salt)
601-
.deploy(token_wasm_hash)
613+
// `deploy_v2` invokes the deployed contract's `__constructor` with
614+
// these arguments, in the same transaction as the deployment.
615+
.deploy_v2(token_wasm_hash, (admin, decimal, name, symbol))
602616
}
603617
```
604618

@@ -609,30 +623,28 @@ pub fn create_contract(e: &Env, token_wasm_hash: BytesN<32>, token: &Address) ->
609623
```rust
610624
//! This contract demonstrates a sample implementation of the Soroban token
611625
//! interface.
612-
use crate::admin::{has_administrator, read_administrator, write_administrator};
626+
use crate::admin::{read_administrator, write_administrator};
613627
use crate::allowance::{read_allowance, spend_allowance, write_allowance};
614628
use crate::balance::{is_authorized, write_authorization};
615629
use crate::balance::{read_balance, receive_balance, spend_balance};
616630
use crate::event;
617631
use crate::metadata::{read_decimal, read_name, read_symbol, write_metadata};
618632
use crate::storage_types::INSTANCE_TTL_EXTEND_AMOUNT;
619-
use soroban_sdk::{contract, contractimpl, Address, Env, String};
633+
use soroban_sdk::{contract, contractimpl, Address, Env, MuxedAddress, String};
620634
use soroban_token_sdk::TokenMetadata;
621635

622636
pub trait TokenTrait {
623-
fn initialize(e: Env, admin: Address, decimal: u32, name: String, symbol: String);
624-
625637
fn allowance(e: Env, from: Address, spender: Address) -> i128;
626638

627-
fn approve(e: Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32);
639+
fn approve(e: Env, from: Address, spender: Address, amount: i128, live_until_ledger: u32);
628640

629641
fn balance(e: Env, id: Address) -> i128;
630642

631643
fn spendable_balance(e: Env, id: Address) -> i128;
632644

633645
fn authorized(e: Env, id: Address) -> bool;
634646

635-
fn transfer(e: Env, from: Address, to: Address, amount: i128);
647+
fn transfer(e: Env, from: Address, to_muxed: MuxedAddress, amount: i128);
636648

637649
fn transfer_from(e: Env, spender: Address, from: Address, to: Address, amount: i128);
638650

@@ -664,17 +676,18 @@ fn check_nonnegative_amount(amount: i128) {
664676
#[contract]
665677
pub struct Token;
666678

679+
// `__constructor` is not part of the token interface — it is an inherent
680+
// function on the contract type that the host invokes once, at deployment.
681+
// Because it can only ever run as part of the deploy, it needs no
682+
// "already initialized" guard.
667683
#[contractimpl]
668-
impl TokenTrait for Token {
669-
fn initialize(e: Env, admin: Address, decimal: u32, name: String, symbol: String) {
670-
if has_administrator(&e) {
671-
panic!("already initialized")
672-
}
673-
write_administrator(&e, &admin);
684+
impl Token {
685+
pub fn __constructor(e: Env, admin: Address, decimal: u32, name: String, symbol: String) {
674686
if decimal > 18 {
675687
panic!("Decimal must not be greater than 18");
676688
}
677689

690+
write_administrator(&e, &admin);
678691
write_metadata(
679692
&e,
680693
TokenMetadata {
@@ -684,21 +697,24 @@ impl TokenTrait for Token {
684697
},
685698
)
686699
}
700+
}
687701

702+
#[contractimpl]
703+
impl TokenTrait for Token {
688704
fn allowance(e: Env, from: Address, spender: Address) -> i128 {
689705
e.storage().instance().extend_ttl(INSTANCE_TTL_EXTEND_AMOUNT);
690706
read_allowance(&e, from, spender).amount
691707
}
692708

693-
fn approve(e: Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32) {
709+
fn approve(e: Env, from: Address, spender: Address, amount: i128, live_until_ledger: u32) {
694710
from.require_auth();
695711

696712
check_nonnegative_amount(amount);
697713

698714
e.storage().instance().extend_ttl(INSTANCE_TTL_EXTEND_AMOUNT);
699715

700-
write_allowance(&e, from.clone(), spender.clone(), amount, expiration_ledger);
701-
event::approve(&e, from, spender, amount, expiration_ledger);
716+
write_allowance(&e, from.clone(), spender.clone(), amount, live_until_ledger);
717+
event::approve(&e, from, spender, amount, live_until_ledger);
702718
}
703719

704720
fn balance(e: Env, id: Address) -> i128 {
@@ -716,14 +732,18 @@ impl TokenTrait for Token {
716732
is_authorized(&e, id)
717733
}
718734

719-
fn transfer(e: Env, from: Address, to: Address, amount: i128) {
735+
// The destination is a `MuxedAddress`, so a single account can be shared by
736+
// many end users, each identified by a muxed ID. Resolve it to the
737+
// underlying `Address` before touching balances.
738+
fn transfer(e: Env, from: Address, to_muxed: MuxedAddress, amount: i128) {
720739
from.require_auth();
721740

722741
check_nonnegative_amount(amount);
723742

724743
e.storage().instance().extend_ttl(INSTANCE_TTL_EXTEND_AMOUNT);
725744

726745
spend_balance(&e, from.clone(), amount);
746+
let to: Address = to_muxed.address();
727747
receive_balance(&e, to.clone(), amount);
728748
event::transfer(&e, from, to, amount);
729749
}
@@ -938,26 +958,15 @@ stellar contract build
938958

939959
<TabItem value="deploy token" label="deploy_token.sh">
940960

961+
Everything after the `--` is passed to the token's `__constructor`, so the token is deployed and initialized in one transaction.
962+
941963
```bash
942964
stellar contract deploy \
943965
--wasm soroban_token_contract.wasm \
944966
--source-account <SECRET_KEY> \
945967
--rpc-url https://soroban-testnet.stellar.org:443 \
946-
--network-passphrase 'Test SDF Network ; September 2015'
947-
```
948-
949-
</TabItem>
950-
951-
<TabItem value="initialize token" label="initialize_token.sh">
952-
953-
```bash
954-
stellar contract invoke \
955-
--id <TOKEN_CONTRACT_ID> \
956-
--source-account <SECRET_KEY> \
957-
--rpc-url https://soroban-testnet.stellar.org:443 \
958968
--network-passphrase 'Test SDF Network ; September 2015' \
959969
-- \
960-
initialize \
961970
--admin <USER_ADDRESS> \
962971
--decimal 18 \
963972
--name <TOKEN_NAME> \
@@ -997,10 +1006,10 @@ stellar contract invoke \
9971006

9981007
</TabItem>
9991008

1000-
<TabItem value="install wasm" label="install.sh">
1009+
<TabItem value="upload wasm" label="upload.sh">
10011010

10021011
```bash
1003-
stellar contract install \
1012+
stellar contract upload \
10041013
--wasm soroban_token_contract.wasm \
10051014
--source-account <SECRET_KEY> \
10061015
--rpc-url https://soroban-testnet.stellar.org:443 \
@@ -1106,38 +1115,27 @@ First, we need to build the vault contract. We can do this by running the `build
11061115
stellar contract build
11071116
```
11081117

1109-
Next, we need to deploy the token contract. We can do this by running the `deploy_token.sh` script.
1118+
Next, we need to deploy the token contract. We can do this by running the `deploy_token.sh` script. The arguments after the `--` are the token's constructor arguments, so this single command both deploys the token and sets its admin and metadata.
11101119

11111120
```bash
11121121
stellar contract deploy \
11131122
--wasm soroban_token_contract.wasm \
11141123
--source-account <SECRET_KEY> \
11151124
--rpc-url https://soroban-testnet.stellar.org:443 \
1116-
--network-passphrase 'Test SDF Network ; September 2015'
1117-
```
1118-
1119-
We should receive an output with the token contract ID. We will need this ID for the next step.
1120-
1121-
```bash
1122-
CBYMG7OPIT67AG4S2FZU7LAYCXUSXEHRGHLDE6H26VCVWNOV7QUQTGNU
1123-
```
1124-
1125-
Next we need to initialize the token contract. We can do this by running the `initialize_token.sh` script.
1126-
1127-
```bash
1128-
stellar contract invoke \
1129-
--id <TOKEN_CONTRACT_ID> \
1130-
--source-account <SECRET_KEY> \
1131-
--rpc-url https://soroban-testnet.stellar.org:443 \
11321125
--network-passphrase 'Test SDF Network ; September 2015' \
11331126
-- \
1134-
initialize \
11351127
--admin <USER_ADDRESS> \
11361128
--decimal 18 \
11371129
--name <TOKEN_NAME> \
11381130
--symbol <TOKEN_SYMBOL>
11391131
```
11401132

1133+
We should receive an output with the token contract ID. We will need this ID for the next step. There is no follow-up initialization call — the constructor already ran.
1134+
1135+
```bash
1136+
CBYMG7OPIT67AG4S2FZU7LAYCXUSXEHRGHLDE6H26VCVWNOV7QUQTGNU
1137+
```
1138+
11411139
Next, we need to deploy the vault contract. We can do this by running the `deploy_vault.sh` script.
11421140

11431141
```bash
@@ -1154,10 +1152,10 @@ We should receive an output with the vault contract ID. We will need this ID for
11541152
CBBPLE6TGYOMO5HUF2AMYLSYYXM2VYZVAVYI5QCCM5OCFRZPBE2XA53F
11551153
```
11561154

1157-
Now we need to get the Wasm hash of the token contract. We can do this by running the `get_token_wasm_hash.sh` script.
1155+
Now we need to get the Wasm hash of the token contract. We can do this by running the `upload.sh` script.
11581156

11591157
```bash
1160-
stellar contract install \
1158+
stellar contract upload \
11611159
--wasm soroban_token_contract.wasm \
11621160
--source-account <SECRET_KEY> \
11631161
--rpc-url https://soroban-testnet.stellar.org:443 \

0 commit comments

Comments
 (0)