Subscribe to our free newsletter

To make sure you won't miss any valuable content we share with our community.

Interacting with Local Terra Smart Contracts Using Python

In this article, we first connect to local Terra using Docker. Then we try to interact with local Terra smart contracts using Python scripts and at the same time use the CosmWasm functions inside LocalTerra\src folder written in Rust programming language. Finally, we will manage to interact with it.

Connecting to Local Terra with Docker with Python

First off, To start our interaction with local Terra, we need to make sure docker is running: sudo usermod -aG docker $USER Make sure you sign in to your docker profile to be able to easily log in using the following command docker login. docker-compose up Result: terrad_1 | 8:48PM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"3F119B6520D17CCC29A23F016B9E40151E3A5784A3F83D65BD0DD2D282FB1A4F"," parts": {"hash":"5928F59D217852E3722E46A9134D40F9329D540555EAD1DAE4B5EFA8A41E88D4"," total":1}},"height":1948,"pol_round":- 1,"round":0,"signature":"cYh80NzZezistnyNzEP2yFDnsoc7JOB+/8J1wnell- Gq76IE3qvH9jU2KvQx6u4y+A9xYrIQEJonzxa//vOZfBw==","timestamp":"2022- 05-08T20:48:12.151658182Z"} terrad_1 | 8:48PM INF received complete proposal block hash=3F119B6520D17CCC29A23F016B9E40151E3A5784A3F83D65BD0DD2D282FB1A4F height=1948 module=consensus terrad_1 | 8:48PM INF finalizing commit of block hash=3F119B6520D17CCC29A23F016B9E40151E3A5784A3F83D65BD0DD2D282FB1A4F height=1948 module=consensus num_txs=0 root=332F90B4F0706BCB055547- FA8443A1F623FC3DAE4043162730F59D15612D5FAE terrad_1 | 8:48PM INF minted coins from module account amount=226578914uluna from=mint module=x/bank terrad_1 | 8:48PM INF executed block height=1948 module=state num_invalid_txs=0 num_valid_txs=0 terrad_1 | 8:48PM INF commit synced commit=436F6D6D697449447B5B393820323020343020323436203134362031303 32036342032313320393520313132203131372031353920393320313536203231312 03632203332203836203137203132352032333920323131203533203337203139352 03637203739203635203830203235322032203138355D3A3739437D terrad_1 | 8:48PM INF committed state app_hash=621428F6926740D55F70759F5D9CD33E2056117DEFD33525C3434F41 50FC02B9 height=1948 module=state num_txs=0 terrad_1 | 8:48PM INF indexed block height=1948 module=txindex terrad_1 | 8:48PM INF Timed out dur=4990.525864 height=1949 module=consensus round=0 step=1 terrad_1 | 8:48PM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"43FEBF954F5FFEA7350E4D8D38FCCA39E439F2D3026ADE9ED3E21D2706AC2596"," parts": {"hash":"112F8B0B6AA51D1D0FCCE2D049F739EC7FEB9B6C2092B1141A55E88AC453FF57"," total":1}},"height":1 949,"pol_round":-1,"round":0,"signature":"tuusiNhk5kRrKpLykCNWb/4b1nrfk22n- RCyaLh650ouCugzK7rGOHq+4eQvBu3HQ3/Y4mF/RFbA67tqO2+nrBQ==","timestamp":" 2022-05-08T20:48:17.164422986Z"} terrad_1 | 8:48PM INF received complete proposal block hash=43FEBF954F5FFEA7350E4D8D38FCCA39E439F2D3026ADE9ED3E21D2706AC2596 height=1949 module=consensus terrad_1 | 8:48PM INF finalizing commit of block hash=43FEBF954F5FFEA7350E4D8D38FCCA39E439F2D3026ADE9ED3E21D2706AC2596 height=1949 module=consensus num_txs=0 root=621428F6926740D55F70759F5D9CD33E2056117DEFD33525C3434F4150FC 02B9 terrad_1 | 8:48PM INF minted coins from module account amount=226578918uluna from=mint module=x/bank terrad_1 | 8:48PM INF executed block height=1949 module=state num_invalid_txs=0 num_valid_txs=0 terrad_1 | 8:48PM INF commit synced commit=436F6D6D697449447B5B352032323420323437203734203133382033312 03231322035322032343220313637203134342037382038332036392032303720313 93920313335203337203230342032382033362031353320333920313930203232382 0333020313434203538203138342032343620313835203230375D3A3739447D terrad_1 | 8:48PM INF committed state app_hash=05E0F74A8A1FD434F2A7904E5345CFC78725CC1C249927BEE41E903AB8F6B9CF height=1949 module=state num_txs=0 terrad_1 | 8:48PM INF indexed block height=1949 module=txindex terrad_1 | 8:48PM INF Timed out dur=4987.072234 height=1950 module=consensus round=0 step=1 terrad_1 | 8:48PM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"5E1C9EA0F662335- DAD4936E50C904AF1FF3D8BBF1083A967A8C4D8EA762942F7","parts": {"hash":"0C3DA2F2B1CDCDF56C432F85E3EE1B0A21064EF69E4DB2D5CAFBE0609FDDE699"," total":1}},"height":1950,"pol_round":- 1,"round":0,"signature":"ZQeA6WQaamDpW07PD64ezc+U1vs/IW3XzMp/yShIIMbJChJFiOJwDB+ dtipWPORdKHNrWVVFlei4drGlx70iCg==","timestamp":"2022- 05-08T20:48:22.181798507Z"} terrad_1 | 8:48PM INF received complete proposal block hash=5E1C9EA0F662335DAD4936E50C904AF1FF3D8BBF1083A967A8C4D8EA762942F7 height=1950 module=consensus terrad_1 | 8:48PM INF finalizing commit of block hash=5E1C9EA0F662335- DAD4936E50C904AF1FF3D8BBF1083A967A8C4D8EA762942F7 height=1950 module=consensus num_txs=0 root=05E0F74A8A1FD434F2A7904E5345CFC78725CC1C249927BEE41E903AB8F6B9CF terrad_1 | 8:48PM INF minted coins from module account amount=226578923uluna from=mint module=x/bank terrad_1 | 8:48PM INF executed block height=1950 module=state num_invalid_txs=0 num_valid_txs=0 terrad_1 | 8:48PM INF commit synced commit=436F6D6D697449447B5B323133203133342032343920393320313830203 13234203136342033372033342032313620313733203236203734203133322032343 22032352031383520313731203230322037302032333520313635203339203235352 0323532203233302035312037203134203135203537203130335D3A3739457D terrad_1 | 8:48PM INF committed state app_hash=D586F95DB47- CA42522D8AD1A4A84F219B9ABCA46EBA527FFFCE633070E0F3967 height=1950 module=state num_txs=0 terrad_1 | 8:48PM INF indexed block height=1950 module=txindex terrad_1 | 8:48PM INF Timed out dur=4983.73452 height=1951 module=consensus round=0 step=1 terrad_1 | 8:48PM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"2D43F5FF6625BD376D71F60269615A5E556BEFF85FC44461B05AF17E54958490"," parts": {"hash":"8377DFE5E66B80B3B9DA5894F5778DF1585741A1C718626AE14167158F7A5DAE"," total":1}},"height":1951,"pol_round ":-1,"round":0,"signature":"Xtc1Wggm2dD/n3YNFjvykNUmXFkN9gNuVL/ yKab4js2DMl2xGJ7ZAjDW/0RceJIBle9SWX13smUamxfsd2wbCQ==","timestamp":" 2022-05-08T20:48:27.197935715Z"} terrad_1 | 8:48PM INF received complete proposal block hash=2D43F5FF6625BD376D71F60269615A5E556BEFF85FC44461B05AF17E54958490 height=1951 module=consensus terrad_1 | 8:48PM INF finalizing commit of block hash=2D43F5FF6625BD376D71F60269615A5E556BEFF85FC44461B05AF17E54958490 height=1951 module=consensus num_txs=1 root=D586F95DB47- CA42522D8AD1A4A84F219B9ABCA46EBA527FFFCE633070E0F3967 terrad_1 | 8:48PM INF minted coins from module account amount=226578928uluna from=mint module=x/bank terrad_1 | 8:48PM INF executed block height=1951 module=state num_invalid_txs=0 num_valid_txs=1

Interacting with Local Terra Using Python: Storing the Contract

Then you can create a new folder inside the local Terra directory and name it testing. Then create another new folder inside the testing folder and name it artifacts. From the artifacts folder of some of the created projects in the local Terra directory (Like NewProjectName), copy the .wasm file and paste it into the new artifacts folder. After that create a new python file inside of the testing folder and name it contract.py. You can write the following code in the contract.py.
		
from terra_sdk.client.localterra import LocalTerra
from terra_sdk.util.contract import read_file_as_b64
from terra_sdk.core.fee import Fee
from terra_sdk.client.lcd.api.tx import CreateTxOptions
from terra_sdk.core.wasm import MsgStoreCode

lt = LocalTerra()
deployer = lt.wallets["test1"]

def store_contract(contract_name:str) -> str:
	contract_bytes = read_file_as_b64(f"artifacts/{contract_name}.wasm")
	store_code = MsgStoreCode(
	deployer.key.acc_address,
	contract_bytes
	)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [store_code],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	print(result)

store_contract("Testing")

		
	
In the above code, at first, we connect to local Terra, then we create a wallet for the deployer of the contract (which is us). After that, we create a function called store contract, inside which we read the .wasm file in the artifacts folder and store it in a variable called contract_bytes. Then we create a message called the MsgStoreCode using the contract_bytes variable and after that, we create and sign a transaction using the store_code message. Finally, we broadcast the transaction and use the store_contract function with the name of the contract called Testing (The name of our project folder).

And run it in another bash, so as not to interfere with the docker running the local Terra, use the following command to start our first interaction with Terra network. python3 contract.py Result: BlockTxBroadcastResult(height=1626, txhash='1143BD1F71A9E951F3B99D7EEE4BF0EE304F0C6C4A63DADA96F7CE243F25F39A', raw_log='[{"events":[{"type":"message","attributes": [{"key":"action","value":"/terra.wasm.v1beta1.MsgStoreCode"}, {"key":"module","value":"wasm"}]},{"type":"store_code","attributes": [{"key":"sender","value":"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"}, {"key":"code_id","value":"1"}]}]}]', gas_wanted=4000000, gas_used=1606062, logs=[TxLog(msg_index=0, log='', events=[{'type': 'message', 'attributes': [{'key': 'action', 'value': '/terra.wasm.v1beta1.MsgStoreCode'}, {'key': 'module', 'value': 'wasm'}]}, {'type': 'store_code', 'attributes': [{'key': 'sender', 'value': 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'}, {'key': 'code_id', 'value': '1'}]}], events_by_type={'message': {'action': ['/terra.wasm.v1beta1.MsgStoreCode'], 'module': ['wasm']}, 'store_code': {'sender': ['terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'], 'code_id': ['1']}})], code=0, codespace='', info=None, data=None, timestamp= None) In the above dictionary, you can see data like txhash (transaction hash), code_id, sender of the store_code, and so on.

Interacting with Local Terra Using Python: Getting the Code ID

Now, let’s get the code id of the transaction using the very same code and making some small changes:
		
from terra_sdk.client.localterra import LocalTerra
from terra_sdk.util.contract import read_file_as_b64,get_code_id
from terra_sdk.core.fee import Fee
from terra_sdk.client.lcd.api.tx import CreateTxOptions
from terra_sdk.core.wasm import MsgStoreCode

lt = LocalTerra()
deployer = lt.wallets["test1"]

def store_contract(contract_name:str) -> str:
	contract_bytes = read_file_as_b64(f"artifacts/{contract_name}.wasm")
	store_code = MsgStoreCode(
	deployer.key.acc_address,
	contract_bytes
	)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [store_code],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	code_id = get_code_id(result)
	return(code_id)

print(store_contract("Testing"))

		
	
We have written the very same code as the previous one with the difference that this time we have specifically returned the code_id in the dictionary and have printed it. Now, let’s run the code using the following command on the terminal, and remember not to lose your connection with local Terra (keep the docker up and running). python3 contract.py Result: 3 Let us try another time: python3 contract.py Result: 4 And one more time to make sure everything works as it should: python3 contract.py Result: 5

Sending JSON Messages to Local Terra Smart Contracts Using Python

The following article is the continuation of the previous one and here we try to interact with local Terra smart contracts using python scripts and at the same time using the CosmWasm functions inside LocalTerra\src folder written in Rust programming language. We will instantiate from the contract and execute it by sending JSON messages to Local Terra smart contracts.

Instantiating form the Contract of the LocalTerra

In the following code, we use the MsgInstantiateContract from terra_sdk.core.wasm to instantiate from the contract, using the code_id and return the contract address:
		
from terra_sdk.client.localterra import LocalTerra
from terra_sdk.util.contract import read_file_as_b64,get_code_id, get_contract_address
from terra_sdk.core.fee import Fee
from terra_sdk.client.lcd.api.tx import CreateTxOptions
from terra_sdk.core.wasm import MsgStoreCode,MsgInstantiateContract

lt = LocalTerra()
deployer = lt.wallets["test1"]

def store_contract(contract_name:str) -> str:
	contract_bytes = read_file_as_b64(f"artifacts/{contract_name}.wasm")
	store_code = MsgStoreCode(
		deployer.key.acc_address,
		contract_bytes
		)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [store_code],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	code_id = get_code_id(result)
	return(code_id)

def instantiate_contract(code_id: str,init_msg)-> str:
	instantiate = MsgInstantiateContract(
		deployer.key.acc_address,
		deployer.key.acc_address,
		code_id = code_id,
		init_msg = init_msg,
		)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [instantiate],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	contract_address = get_contract_address(result)
	return contract_address

code_id = store_contract("Testing")
contract_address = instantiate_contract(code_id,{"count": 15})
print(code_id, contract_address)

		
	
The goal of the above code is to instantiate from the contract using the code_id of the store_contract transaction. To do so, we define a function called instantiate_contract. Inside this function, we create a message with MsgInstantiateContract function. Then we will create and sign the transaction using the created message. Finally, we will broadcast the transaction and return the contract address. Notice that the function that we have defined has 2 attributes: code_id and init_msg. When we call this function, we can enter the code_id we have got from the output of store_contract. For init_msg, we enter the {"count": number} in which number can be any unsigned integer number. In the end, we print the code_id with the contract address which is the output of the instantiate_contract function.

Now, let’s try our script and see if it prints out the code_id and the contract address: python3 contract.py Result: 11 terra1wkgucw0zply6e5c4h30a4z5qljhazepw4jpf2s Try another time: python3 contract.py Result: 12 terra1mhf9cr9f70rd052rptnevcje2puq6heekrxdwa As you can see the code_id has incremented.

And another time: python3 contract.py Result: 13 terra1uw258kwqftsywjfv972y4d3f4mn0hrlkpt7y3y

Executing the Contract

Now, using the MsgExecuteContract, contract address, and account address, we execute the contract inside the local Terra directory.
		
from terra_sdk.client.localterra import LocalTerra
from terra_sdk.util.contract import read_file_as_b64,get_code_id, get_contract_address
from terra_sdk.core.fee import Fee
from terra_sdk.client.lcd.api.tx import CreateTxOptions
from terra_sdk.core.wasm import MsgStoreCode,MsgInstantiateContract, MsgExecuteContract

lt = LocalTerra()
deployer = lt.wallets["test1"]

def store_contract(contract_name:str) -> str:
	contract_bytes = read_file_as_b64(f"artifacts/{contract_name}.wasm")
	store_code = MsgStoreCode(
	deployer.key.acc_address,
	contract_bytes
	)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [store_code],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	code_id = get_code_id(result)
	return(code_id)

def instantiate_contract(code_id: str,init_msg)-> str:
	instantiate = MsgInstantiateContract(
		deployer.key.acc_address,
		deployer.key.acc_address,
		code_id = code_id,
		init_msg = init_msg,
		)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [instantiate],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	contract_address = get_contract_address(result)
	return contract_address

def execute_contract(sender,contract_addr: str, execute_msg):
	execute = MsgExecuteContract(
	sender = sender.key.acc_address, contract = contract_addr,execute_msg = execute_msg)
	tx = sender.create_and_sign_tx(CreateTxOptions(
		msgs = [execute],
		fee = Fee(4000000,"10000000uluna")
		))
	)
	result = lt.tx.broadcast(tx)
	return result

code_id = store_contract("Testing")
contract_address = instantiate_contract(code_id,{"count": 15})
execute = execute_contract(deployer,contract_address,{"increment":{}})
execute1 = execute_contract(deployer,contract_address,{"increment":{}})
execute2 = execute_contract(deployer,contract_address,{"increment":{}})
execute3 = execute_contract(deployer,contract_address,{"increment":{}})
execute4 = execute_contract(deployer,contract_address,{"increment":{}})

print(execute1)
print(lt.wasm.contract_query(contract_address,{"get_count":{}}))
print(code_id, contract_address)
		
	
In the above Python script, we will use the execute_contract next to store_contract and instantiate_contract functions. In the execute function, we have done the 3 main jobs we did for other functions, creating the message, creating and signing the transaction , and broadcasting it. The execute function has 3 attributes:
  1. 1. The sender (deployer of the contract).
  2. 2. The contract address that we have got from instantiate_contract function.
  3. 3. The execute message which can be increment, reset ,etc.
Outside the execute function, we call it 5 times with the increment message. And to see the results, we print the second execution and also the query result of the get_count message which is going to check if we have successfully executed the contract 5 times. If so, the result must show that the count = 20. (15 +1+1+1+1+1 = 20).

Now, let’s see if our code prints out the result of local Terra contract execution, as well as the code_id, query result, and the contract address: python3 contract.py Result: BlockTxBroadcastResult(height=8030, txhash='F51A179323A3489F4629FB0ECA4B83C64237961F9244409EFBDBF88EFC8CFA97', raw_log='[{"events": [{"type":"execute_contract","attributes":[{"key":"sender","value":"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"}, {"key":"contract_address","value":"terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz"}]},{" type":"from_contract","attributes":[{"key":"contract_ address","value":"terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz"}, {"key":"method","value":"try_increment"}]},{"type":"message","attributes": [{"key":"action","value":"/terra.wasm.v1beta1.MsgExecuteContract"},{"key":"module"," value":"wasm"},{"key":"sender","value":"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"}]},{" type":"wasm","attributes": [{"key":"contract_address","value":"terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz"},{" key":"method","value":"try_increment"}]}]}]', gas_wanted=4000000, gas_used=102492, logs=[TxLog(msg_index=0, log='', events=[{'type': 'execute_contract', 'attributes': [{'key': 'sender', 'value': 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'}, {'key': 'contract_address', 'value': 'terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz'}]}, {'type': 'from_contract', 'attributes': [{'key': 'contract_address', 'value': 'terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz'}, {'key': 'method', 'value': 'try_increment'}]}, {'type': 'message', 'attributes': [{'key': 'action', 'value': '/terra.wasm.v1beta1.MsgExecuteContract'}, {'key': 'module', 'value': 'wasm'}, {'key': 'sender', 'value': 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'}]}, {'type': 'wasm', 'attributes': [{'key': 'contract_address', 'value': 'terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz'}, {'key': 'method', 'value': 'try_increment'}]}], events_by_type={'execute_contract': {'sender': ['terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'], 'contract_address': ['terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz']}, 'from_contract': {'contract_address': ['terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz'], 'method': ['try_increment']}, 'message': {'action': ['/terra.wasm.v1beta1.MsgExecuteContract'], 'module': ['wasm'], 'sender': ['terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v']}, 'wasm': {'contract_address': ['terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz'], 'method': ['try_increment']}})], code=0, codespace='', info=None, data=None, timestamp=None) {'count': 20} 17 terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz Notice that it takes a few seconds for the contract to be instantiated and the transaction to be executed. As we have determined the count = 15 in the instantiate_contract function and executed the contract with the increment message 5 times, The result of the query count will be 20.

Try one more time, and you will see the same result with the code_id incremented. python3 contract.py Result: BlockTxBroadcastResult(height=8269, txhash='73C112C7B0DA968209310068DFCA9C26378A47EA383C3A4DBE350DCFE54079E8', raw_log='[{"events": [{"type":"execute_contract","attributes":[{"key":"sender","value":"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"}, {"key":"contract_address","value":"terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra"}]},{" type":"from_contract","attributes": [{"key":"contract_address","value":"terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra"},{" key":"method","value":"try_increment"}]},{"type":"message","attributes": [{"key":"action","value":"/terra.wasm.v1beta1.MsgExecuteContract"},{"key":"module"," value":"wasm"},{"key":"sender","value":"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"}]},{" type":"wasm","attributes": [{"key":"contract_address","value":"terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra"},{" key":"method","value":"try_increment"}]}]}]', gas_wanted=4000000, gas_used=102492, logs=[TxLog(msg_index=0, log='', events=[{'type': 'execute_contract', 'attributes': [{'key': 'sender', 'value': 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'}, {'key': 'contract_address', 'value': 'terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra'}]}, {'type': 'from_contract', 'attributes': [{'key': 'contract_address', 'value': 'terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra'}, {'key': 'method', 'value': 'try_increment'}]}, {'type': 'message', 'attributes': [{'key': 'action', 'value': '/terra.wasm.v1beta1.MsgExecuteContract'}, {'key': 'module', 'value': 'wasm'}, {'key': 'sender', 'value': 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'}]}, {'type': 'wasm', 'attributes': [{'key': 'contract_address', 'value': 'terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra'}, {'key': 'method', 'value': 'try_increment'}]}], events_by_type={'execute_contract': {'sender': ['terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'], 'contract_address': ['terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra']}, 'from_contract': {'contract_address': ['terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra'], 'method': ['try_increment']}, 'message': {'action': ['/terra.wasm.v1beta1.MsgExecuteContract'], 'module': ['wasm'], 'sender': ['terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v']}, 'wasm': {'contract_address': ['terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra'], 'method': ['try_increment']}})], code=0, codespace='', info=None, data=None, timestamp=None) {'count': 20} 18 terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra

Wrapping Up

In this article, we have managed to connect to local Terra using Docker and interact with it by the means of Python scripts. In detail, we have managed to store the contract and get the code_id of the transaction. In the next parts, we will focus on the other functions such as instantiating and executing the contract to complete the process of interacting with Terra smart contracts.

Finally, we have managed to interact with local Terra using the Python scripts. At first, we connected to local Terra using Docker. Then, we created a wallet, that, stored our contract, instantiated from the stored contract using the code_id, and executed it.

Download this Article in PDF format

3d websites

Care to Know Us More?

In Arashtad, we have gathered a professional team of developers who are working in fields such as 3D websites, 3D games, metaverses, and other types of WebGL and 3D applications as well as blockchain developemnet.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Fill in the Form
Blockchain Development

How to Write Terra Smart Contracts: A Complete Tutorial

This article explains the connections between the python codes we wrote earlier (same for JavaScript) and the pre-built functions inside the src folder created with the cargo generate–git command. Reading this article will give you a deeper insight into what actually happens when we instantiate, execute or query a state of Terra smart contracts in python code and how it communicates with the CosmWasm libraries written in Rust. Being familiar with Rust programming language can help you better understand the steps we are going to take to interact with the CosmWasm smart contracts on the Terra network. We have provided a small Rust tutorial to make you more familiar with Rust scripts at CosmWasm Smart Contracts – learning Rust series.

What Are Terra Smart Contracts?

In the blockchain, Terra smart contracts are instances of a singleton object whose internal state is persisted on the blockchain. A singleton is a class that allows only a single instance of itself to be created and gives access to that created instance. Users can query or set the state changes by sending a JSON message. The details of these messages are going to be explained throughout this article. Terra smart contracts are defined by these 3 main functions:
    1. Instantiate(): a constructor which is used during contract instantiation to set the initial state.
    2. Execute(): it is used when a user wants to invoke a method on the smart contract.
    3. Query(): it is used when a user wants to get data out of a smart contract.

Creating the Template to Write Terra Smart Contracts

The 1st step of creating a smart contract is to create the template using the command below: cargo generate --git https://github.com/CosmWasm/cw-template.git --branch 0.16 -- name my-first-contract cd my-first-contract The above cargo command creates a structure and boilerplate for creating Terra smart contract. A sample smart contract has the following template, we should define a struct called state and in it, we should determine the count and the address of the owner of the contract. The below code can also be found in the src folder, state.rs file.

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::Addr;
use cw_storage_plus::Item;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct State {
     pub count: i32,
     pub owner: Addr,
}
pub const STATE: Item = Item::new("state");

Serialization & Deserialization

Notice that some data, like owner and count, can only be persisted as bytes, so we need to convert these data from human-readable format to bytes. That is why we use serialization and deserialization. The CosmWasm team has provided the utility crates such as cosmwasm_storage, which automatically provides serialization and deserialization for commonly used types such as Structs and numbers in Rust. Some of the useful traits applied by deriving attributes in #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] are as follows:
    1. Serialize: for providing serialization
    2. Deserialize: for deserialization
    3. Clone: makes the struct copyable
    4. Debug: enables the struct to be printed to string
    5. PartialEq: provides equality comparison
    6. JsonSchema: automatically creates a JSON schema

InstantiateMsg

The instantiateMsg is provided for the users when they want to use MsgInstantiate Contract in their script (Python or JavaScript). This provides the initial state and the configuration of the contract. As opposed to Ethereum, in Terra Smart Contracts, the uploading of a smart contract code and the instantiation of it are considered 2 separate events. The reason for this is to allow different contracts to have the same base code by instantiating. The below code can be found in the src folder and inside the file called msg.rs.

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
     pub count: i32,
}


To create Terra smart contracts, you can send a JSON message like this:

{
     "count": 100
}


Remember in the python scripts (contracts.py) when we sent the instantiateMsg and set the JSON message to {“count”: 15}:

contract_address = instantiate_contract(code_id,{"count": 15})


When the message InstantiateMsg is sent, the below rust script in the src/contract.rs will extract the message and set up the initial state. In this instantiate function, the count is the extracted count from the message and the address is the address of the account that sent the MsgInstantiateContract message.

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
               deps: DepsMut,
               _env: Env,
               info: MessageInfo,
               msg: InstantiateMsg,
          ) -> Result {
     let state = State {
     count: msg.count,
     owner: info.sender.clone(),
     };
     set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
     STATE.save(deps.storage, &state)?;
     Ok(Response::new()
     .add_attribute("method", "instantiate")
     .add_attribute("owner", info.sender)
     .add_attribute("count", msg.count.to_string()))
}


ExecuteMsg

The ExecuteMsg in python or Javascript is the message sent to the execute function in contract.rs through MsgExecuteContract (remember when we imported this in our contract.py). Opposite to what we had in InstantiateMsg, in the ExecuteMsg we have different messages for executing different functionalities(logics). The execute function in the contract.rs file demultiplexes these different messages to their related logic. In the msg.rs file, you will see the definition of the 2 messages Increment and Reset in a public enum.

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
     Increment {},
     Reset { count: i32 },
}


And as you can see, in the contract.rs, Increment and Reset are connected to their appropriate functions in the match msg:

ExecuteMsg::Increment {} => try_increment(deps)
Increment {} => try_increment(deps),

The complete code of execute function goes like this:

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
               deps: DepsMut,
               _env: Env,
               info: MessageInfo,
               msg: ExecuteMsg,
          ) -> Result {
     match msg {
          ExecuteMsg::Increment {} => try_increment(deps),
          ExecuteMsg::Reset { count } => try_reset(deps, info, count),
     }
}


Now, the messages that we send in a python or Javascript code, goes like this: increment message:

{
     "increment": {}
}


reset message:

{
     "reset": {"count": 5}
}

remember the kind of script we wrote in python to send the increment message.

execute = execute_contract(deployer,contract_address,{"increment":{}})


Now, let’s see how try_increment and try_reset functions work in the contract.rs file. In the try_increment function, the mutable state from state.rs is called. Then, it is updated (incremented) to a new number and then the function returns OK with a new state. Finally the execution is finalized as successful by the OK response.

pub fn try_increment(deps: DepsMut) -> Result {
     STATE.update(deps.storage, |mut state| -> Result<_, ContractError> {
          state.count += 1;
          Ok(state)
     })?;
     Ok(Response::new().add_attribute("method", "try_increment"))
}


The try_reset works the same way as try_increment with the difference that it first checks whether the sender of the message is the owner or not.

pub fn try_reset(deps: DepsMut, info: MessageInfo, count: i32) -> Result {
     STATE.update(deps.storage, |mut state| -> Result<_, ContractError> {
          if info.sender != state.owner {
               return Err(ContractError::Unauthorized {});
          }
          state.count = count;
          Ok(state)
     })?;
     Ok(Response::new().add_attribute("method", "reset"))
}


How to Interact with Terra Smart Contracts

In the continuation of this journey, we are going to see how the QueryMsg function is defined in Rust in addition to how we can interact with it using Python or JavaScript. QueryMsg: For the query messages, there are two functionalities required:
    1. A format of receiving defined in the request GetCount {} inside the QueryMsg enum.
    2. And a variable pub count: i32 to respond to the query request which is defined inside the CountResponse struct. The scripts below can be found in the src folder inside the file called msg.rs

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
     // GetCount returns the current count as a json-encoded number
     GetCount {},
}
// Define a custom struct for each query response
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct CountResponse {
     pub count: i32,
}

The following script can be found inside the contract.rs file in the src folder. This script contains 2 functions, one for receiving the query request and two for returning the responding of the state of the contract from the state.rs file. The query function receives the query request and converts the message to binary and passes it to the query_count function which loads the state of the contract from the state.rs and returns the result with the OK response and the message in the { count: state.count } format.


#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
     match msg {
          QueryMsg::GetCount {} => to_binary(&query_count(deps)?),
     }
}

fn query_count(deps: Deps) -> StdResult {
     let state = STATE.load(deps.storage)?;
     Ok(CountResponse { count: state.count })
}


The message we send for querying the state of the contract is in this format.

{
     "get_count": {}
}


And the respond we get is like the following:

{
     "count": 5
}

Building Terra Smart Contract

To build the contract, you can use the 2 commands below: cargo test
cargo wasm
The next thing we do is optimize our build by running the following command: cargo run-script optimize To be able to run the above command, you need to make sure you have installed docker.
If you are on arm-64 machine, you should run: docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ cosmwasm/rust-optimizer-arm64:0.12.4 And if you are on windows with docker daemon running on WSL1 run the following: docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ cosmwasm/rust-optimizer-arm64:0.12.4 Any of the above commands if run properly (according to the use cases mentioned), should create a new folder called artifacts which contains the my-first-contract.wasm file. To make JSON Schema files, for serialization, you need to register each of the data structures by export_schema(&schema_for!(), &out_dir); you can see the registration in the examples folder, inside the file called schema.rs:

use std::env::current_dir;
use std::fs::create_dir_all;
use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
use my_first_contract::msg::{CountResponse, HandleMsg, InitMsg, QueryMsg};
use my_first_contract::state::State;
fn main() {
     let mut out_dir = current_dir().unwrap();
     out_dir.push("schema");
     create_dir_all(&out_dir).unwrap();
     remove_schemas(&out_dir).unwrap();
     export_schema(&schema_for!(InstantiateMsg), &out_dir);
     export_schema(&schema_for!(ExecuteMsg), &out_dir);
     export_schema(&schema_for!(QueryMsg), &out_dir);
     export_schema(&schema_for!(State), &out_dir);
     export_schema(&schema_for!(CountResponse), &out_dir);
}

Now, using the following commands, you can create these data structure JSON files inside schema folder: cd examples Cargo schema Result: Finished dev [unoptimized + debuginfo] target(s) in 0.37s Running `/home/mohamad/LocalTerra/my-first-contract.wasm/target/debug/examples/schema` Created /home/mohamad/LocalTerra/my-first-contract.wasm/examples/schema/instantiate_msg.json Created /home/mohamad/LocalTerra/my-first-contract.wasm/examples/schema/execute_msg.json Created /home/mohamad/LocalTerra/my-first-contract.wasm/examples/schema/ query_msg.json Created /home/mohamad/LocalTerra/my-first-contract.wasm/examples/schema/ state.json Created /home/mohamad/LocalTerra/my-first-contract.wasm/examples/schema/ count_response.json Inside the schema folder, you should be able to see all the requested data structures JSON files.

count_response.json
execute_msg.json
instantiate_msg.json
query_msg.json
state.json

Inside the state.json, you can see different sections of the .json file, the most important of which is the requires containing the “count” and the “owner”. { "$schema": "http://json-schema.org/draft-07/schema#", "title": "State", "type": "object", "required": [ "count", "owner" ], "properties": { "count": { "type": "integer", "format": "int32" }, "owner": { "$ref": "#/definitions/Addr" } }, "definitions": { "Addr": { "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Re- ally? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "type": "string" } } } Besides, you can see 2 required sections inside the execute_msg.json: 1. "increment" 2. "reset" { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "anyOf": [ { "type": "object", "required": [ "increment" ], "properties": { "increment": { "type": "object" } }, "additionalProperties": false }, { "type": "object", "required": [ "reset" ], "properties": { "reset": { "type": "object", "required": [ "count" ], "properties": { "count": { "type": "integer", "format": "int32" } } } }, "additionalProperties": false } ] }

Summing Up

we dived deep into the details of the local Terra smart contracts written in Rust or in other words the CosmWasm smart contracts. This type of understanding from scratch helps us interact and communicate better with local Terra and understand what actually happens behind the scenes when we send JSON messages with python scripts to instantiate or execute a smart contract or query data from it. Firstly, we showed how to create a template for initiating state. Then, we created a smart contract and learned how to interact with it. Besides, we learned how to use the QueryMsg function and build a Terra smart contract.

Download this Article in PDF format

metaverse

Curious about What We’re Up To?

In Arashtad, we provide custom services in various design and development fields. 3D websites, 3D games, metaverses, and other types of WebGL and 3D applications are just some of our expertise.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Request a Quote
ThreeJS Development