|
| 1 | +from stellar_sdk import xdr as stellar_xdr |
| 2 | +from stellar_sdk import Network, Keypair, TransactionBuilder, StrKey, utils |
| 3 | +from stellar_sdk.soroban import SorobanServer |
| 4 | +from stellar_sdk.soroban.types import Address, Int128, Bytes |
| 5 | +from stellar_sdk.soroban.soroban_rpc import GetTransactionStatus |
| 6 | +from stellar_sdk.xdr import TransactionMeta, LedgerKey, ConfigUpgradeSet, ConfigSettingContractExecutionLanesV0, ConfigUpgradeSetKey, ConfigSettingEntry, StateExpirationSettings, Uint32, Uint64, Int64, Hash, LedgerKeyConfigSetting, ConfigSettingID |
| 7 | +import stellar_sdk |
| 8 | +from enum import IntEnum |
| 9 | +import urllib.parse |
| 10 | +import argparse |
| 11 | +import time |
| 12 | +import sys |
| 13 | + |
| 14 | +secret = "SAAPYAPTTRZMCUZFPG3G66V4ZMHTK4TWA6NS7U4F7Z3IMUD52EK4DDEV" |
| 15 | +# public -> GDAT5HWTGIU4TSSZ4752OUC4SABDLTLZFRPZUJ3D6LKBNEPA7V2CIG54 |
| 16 | + |
| 17 | +# For standalone |
| 18 | +# Fund standalone with friendbot - http://localhost:8000/friendbot?addr=GDAT5HWTGIU4TSSZ4752OUC4SABDLTLZFRPZUJ3D6LKBNEPA7V2CIG54 |
| 19 | +# rpc_server_url = "http://127.0.0.1:8000/soroban/rpc" |
| 20 | +# network_passphrase = Network.STANDALONE_NETWORK_PASSPHRASE |
| 21 | + |
| 22 | +# For futurenet |
| 23 | +# Fund futurenet with friendbot - https://friendbot-futurenet.stellar.org/?addr=GDAT5HWTGIU4TSSZ4752OUC4SABDLTLZFRPZUJ3D6LKBNEPA7V2CIG54 |
| 24 | +rpc_server_url = "https://rpc-futurenet.stellar.org:443/" |
| 25 | +network_passphrase = Network.FUTURENET_NETWORK_PASSPHRASE |
| 26 | + |
| 27 | +contract_file_path = "write_upgrade_bytes/target/wasm32-unknown-unknown/release/soroban_write_upgrade_bytes_contract.wasm" |
| 28 | + |
| 29 | +kp = Keypair.from_secret(secret) |
| 30 | +soroban_server = SorobanServer(rpc_server_url) |
| 31 | + |
| 32 | +# hardcode the upgrade you want to do here. |
| 33 | +def get_upgrade_set(): |
| 34 | + state_exp_settings = StateExpirationSettings(max_entry_expiration=Uint32(6312000), |
| 35 | + min_temp_entry_expiration=Uint32( |
| 36 | + 16), |
| 37 | + min_persistent_entry_expiration=Uint32( |
| 38 | + 86400), |
| 39 | + auto_bump_ledgers=Uint32(0), |
| 40 | + persistent_rent_rate_denominator=Int64( |
| 41 | + 0), |
| 42 | + temp_rent_rate_denominator=Int64( |
| 43 | + 0), |
| 44 | + max_entries_to_expire=Uint32( |
| 45 | + 0), |
| 46 | + bucket_list_size_window_sample_size=Uint32( |
| 47 | + 0), |
| 48 | + eviction_scan_size=Uint64(0)) |
| 49 | + |
| 50 | + entry = ConfigSettingEntry.from_config_setting_state_expiration( |
| 51 | + state_exp_settings) |
| 52 | + return ConfigUpgradeSet([entry]) |
| 53 | +############# |
| 54 | + |
| 55 | +# TODO: Update tx submissions to go directly to tx endpoint instead of rpc |
| 56 | +# Upload and create contract |
| 57 | + |
| 58 | + |
| 59 | +def deploy_contract(): |
| 60 | + print("uploading contract...") |
| 61 | + source = soroban_server.load_account(kp.public_key) |
| 62 | + tx = ( |
| 63 | + TransactionBuilder(source, network_passphrase) |
| 64 | + .set_timeout(300) |
| 65 | + .append_upload_contract_wasm_op( |
| 66 | + contract=contract_file_path, # the path to the contract, or binary data |
| 67 | + ) |
| 68 | + .build() |
| 69 | + ) |
| 70 | + |
| 71 | + tx = soroban_server.prepare_transaction(tx) |
| 72 | + tx.sign(kp) |
| 73 | + send_transaction_data = soroban_server.send_transaction(tx) |
| 74 | + print(f"sent transaction: {send_transaction_data}") |
| 75 | + |
| 76 | + while True: |
| 77 | + print("waiting for transaction to be confirmed...") |
| 78 | + get_transaction_data = soroban_server.get_transaction( |
| 79 | + send_transaction_data.hash) |
| 80 | + if get_transaction_data.status != GetTransactionStatus.NOT_FOUND: |
| 81 | + break |
| 82 | + time.sleep(3) |
| 83 | + |
| 84 | + print(f"transaction: {get_transaction_data}\n\n") |
| 85 | + |
| 86 | + wasm_id = None |
| 87 | + if get_transaction_data.status == GetTransactionStatus.SUCCESS: |
| 88 | + assert get_transaction_data.result_meta_xdr is not None |
| 89 | + transaction_meta = TransactionMeta.from_xdr( |
| 90 | + get_transaction_data.result_meta_xdr |
| 91 | + ) |
| 92 | + wasm_id = transaction_meta.v3.soroban_meta.return_value.bytes.sc_bytes.hex() # type: ignore |
| 93 | + print(f"wasm id: {wasm_id}") |
| 94 | + |
| 95 | + assert wasm_id, "wasm id should not be empty" |
| 96 | + |
| 97 | + print("creating contract...") |
| 98 | + |
| 99 | + source = soroban_server.load_account( |
| 100 | + kp.public_key |
| 101 | + ) # refresh source account, because the current SDK will increment the sequence number by one after building a transaction |
| 102 | + |
| 103 | + tx = ( |
| 104 | + TransactionBuilder(source, network_passphrase) |
| 105 | + .set_timeout(300) |
| 106 | + .append_create_contract_op( |
| 107 | + wasm_id=wasm_id, |
| 108 | + ) |
| 109 | + .build() |
| 110 | + ) |
| 111 | + |
| 112 | + tx = soroban_server.prepare_transaction(tx) |
| 113 | + tx.sign(kp) |
| 114 | + |
| 115 | + send_transaction_data = soroban_server.send_transaction(tx) |
| 116 | + print(f"sent transaction: {send_transaction_data}") |
| 117 | + |
| 118 | + while True: |
| 119 | + print("waiting for transaction to be confirmed...") |
| 120 | + get_transaction_data = soroban_server.get_transaction( |
| 121 | + send_transaction_data.hash) |
| 122 | + if get_transaction_data.status != GetTransactionStatus.NOT_FOUND: |
| 123 | + break |
| 124 | + time.sleep(3) |
| 125 | + |
| 126 | + print(f"transaction: {get_transaction_data}\n\n") |
| 127 | + |
| 128 | + if get_transaction_data.status == GetTransactionStatus.SUCCESS: |
| 129 | + assert get_transaction_data.result_meta_xdr is not None |
| 130 | + transaction_meta = stellar_xdr.TransactionMeta.from_xdr( |
| 131 | + get_transaction_data.result_meta_xdr |
| 132 | + ) |
| 133 | + result = transaction_meta.v3.soroban_meta.return_value.address.contract_id.hash # type: ignore |
| 134 | + return result |
| 135 | + |
| 136 | + |
| 137 | +def upload_upgrade_bytes(contract_id, upgrade): |
| 138 | + source = soroban_server.load_account(kp.public_key) |
| 139 | + |
| 140 | + tx = ( |
| 141 | + TransactionBuilder(source, network_passphrase, base_fee=100) |
| 142 | + .add_time_bounds(0, 0) |
| 143 | + .append_invoke_contract_function_op( |
| 144 | + contract_id=contract_id, |
| 145 | + function_name="write", |
| 146 | + parameters=[Bytes(upgrade.to_xdr_bytes())], |
| 147 | + ) |
| 148 | + .build() |
| 149 | + ) |
| 150 | + |
| 151 | + tx = soroban_server.prepare_transaction(tx) |
| 152 | + tx.sign(kp) |
| 153 | + send_transaction_data = soroban_server.send_transaction(tx) |
| 154 | + print(f"sent transaction: {send_transaction_data}") |
| 155 | + |
| 156 | + while True: |
| 157 | + print("waiting for transaction to be confirmed...") |
| 158 | + get_transaction_data = soroban_server.get_transaction( |
| 159 | + send_transaction_data.hash) |
| 160 | + if get_transaction_data.status != GetTransactionStatus.NOT_FOUND: |
| 161 | + break |
| 162 | + time.sleep(3) |
| 163 | + |
| 164 | + print(f"transaction: {get_transaction_data}\n\n") |
| 165 | + |
| 166 | + assert get_transaction_data.status == GetTransactionStatus.SUCCESS |
| 167 | + |
| 168 | + |
| 169 | +def get_upgrade_key(contract_id, upgrade_hash): |
| 170 | + key = ConfigUpgradeSetKey(contract_id=Hash( |
| 171 | + contract_id), content_hash=Hash(upgrade_hash)) |
| 172 | + return key |
| 173 | + |
| 174 | + |
| 175 | +""" |
| 176 | +The setup_upgrade mode will setup the ContractData required for the |
| 177 | +upgrade, and then print out the url encoded xdr to submit to core |
| 178 | +using the upgrades endpoint (upgrades?mode=set&configupgradesetkey=********). |
| 179 | +
|
| 180 | +Note that the upgrade is specified in code at the moment (upgrade = get_upgrade_set()) |
| 181 | +""" |
| 182 | + |
| 183 | + |
| 184 | +def setup_upgrade(args): |
| 185 | + # Get upgrade xdr |
| 186 | + |
| 187 | + upgrade = get_upgrade_set() |
| 188 | + upgrade_xdr = upgrade.to_xdr() |
| 189 | + upgrade_hash = utils.sha256(upgrade.to_xdr_bytes()) |
| 190 | + |
| 191 | + # Deploy contract |
| 192 | + contract_id_hash = deploy_contract() |
| 193 | + # print(f"contract id xdr: {Hash(contract_id_hash).to_xdr()}") |
| 194 | + print(f"contract id hex: {Hash(contract_id_hash).to_xdr_bytes().hex()}") |
| 195 | + |
| 196 | + contract_id_str = StrKey.encode_contract(contract_id_hash) |
| 197 | + print(f"contract id: {contract_id_str}") |
| 198 | + |
| 199 | + # Invoke contract |
| 200 | + upload_upgrade_bytes(contract_id_str, upgrade) |
| 201 | + |
| 202 | + # Get upgrade |
| 203 | + upgrade_key = get_upgrade_key(contract_id_hash, upgrade_hash).to_xdr() |
| 204 | + url_encoded_key = urllib.parse.quote(upgrade_key) |
| 205 | + print(f"url encoded upgrade: {url_encoded_key}") |
| 206 | + |
| 207 | + |
| 208 | +def get_settings(args): |
| 209 | + setting_key = LedgerKeyConfigSetting( |
| 210 | + config_setting_id=ConfigSettingID(int(args.configSettingID))) |
| 211 | + ledger_key = LedgerKey.from_config_setting(setting_key) |
| 212 | + |
| 213 | + resp = soroban_server.get_ledger_entries([ledger_key]) |
| 214 | + if resp.entries is None: |
| 215 | + raise RequestException( |
| 216 | + 404, |
| 217 | + f"Ledger entry not found, maybe you need to activate", |
| 218 | + ) |
| 219 | + assert len(resp.entries) == 1 |
| 220 | + data = stellar_xdr.LedgerEntryData.from_xdr(resp.entries[0].xdr) |
| 221 | + assert data.config_setting is not None |
| 222 | + print(data) |
| 223 | + |
| 224 | + |
| 225 | +def get_ledger_key() -> str: |
| 226 | + setting_key = LedgerKeyConfigSetting( |
| 227 | + config_setting_id=ConfigSettingID.CONFIG_SETTING_STATE_EXPIRATION) |
| 228 | + ledger_key = LedgerKey.from_config_setting(setting_key) |
| 229 | + return ledger_key |
| 230 | + |
| 231 | + |
| 232 | +def main(): |
| 233 | + argument_parser = argparse.ArgumentParser() |
| 234 | + subparsers = argument_parser.add_subparsers(required=True) |
| 235 | + |
| 236 | + parser_setup_upgrade = subparsers.add_parser( |
| 237 | + "setupUpgrade", |
| 238 | + help="Get upgrade for hardcoded settings") |
| 239 | + parser_setup_upgrade.set_defaults(func=setup_upgrade) |
| 240 | + |
| 241 | + parser_get_settings = subparsers.add_parser( |
| 242 | + "getSettings", |
| 243 | + help="Get settings") |
| 244 | + parser_get_settings.add_argument("-id", |
| 245 | + "--configSettingID", |
| 246 | + required=True, |
| 247 | + help="The integer ConfigSettingID value being queried") |
| 248 | + parser_get_settings.set_defaults(func=get_settings) |
| 249 | + |
| 250 | + args = argument_parser.parse_args() |
| 251 | + args.func(args) |
| 252 | + |
| 253 | + |
| 254 | +if __name__ == "__main__": |
| 255 | + main() |
0 commit comments