Deploying a Solidity Contract

Let’s suppose that we have a simple Free TON Solidity smart contract:

pragma ton-solidity >= 0.37.0;
pragma AbiHeader expire;

contract HelloWorld {

  string variable ;
  constructor(string arg) public { tvm.accept() ; variable = arg ; }

  function get_var() public view returns (string s)
  { tvm.accept(); s = variable ; }
}

First, let’s try to compile it:

─➤ ft contract build Basic.sol

Calling /home/user/.ft/bin/solc Basic.sol
Code was generated and saved to file Basic.code
ABI was generated and saved to file Basic.abi.json
Calling /home/user/.ft/bin/tvm_linker compile -o Basic.tvm Basic.code --abi-json Basic.abi.json --lib /home/user/.ft/bin/stdlib_sol.tvm
TVM linker 0.1.0
COMMIT_ID: 85973140d89b0da5c211b562be86fba5783815d1
BUILD_DATE: 2021-03-04 16:02:46 +0100
COMMIT_DATE: 2021-02-17 19:22:22 +0300
GIT_BRANCH: master
Calling cp -f Basic.sol Basic.abi.json /home/user/.ft/contracts
Calling cp -f Basic.tvm /home/user/.ft/contracts/Basic.tvc
Config loaded from /home/user/.ft/config.json
Network: sandbox1
Loading wallet file /home/user/.ft/sandbox1/wallet.json

We can see that ft called solc and then tvm_linker to compile and link the contract. The contract was then copied in the contract database of ft under the name Basic (as extracted from the filename).

We will now try to run it locally. For that, let’s create a sandbox network on the current computer:

╰─➤ ft switch create sandbox1

Config loaded from /home/user/.ft/config.json
Network: mainnet
Loading wallet file /home/user/.ft/mainnet/wallet.json
Calling docker create --name local-node-7081 -e USER_AGREEMENT=yes -p7081:80 tonlabs/local-node
274bcd1a5ab7190ad7cb91052b2870dce4bc301affda5eecf91a85791eb6ecde
Saving wallet file /home/user/.ft/mainnet/wallet.json
Saving wallet file /home/user/.ft/sandbox1/wallet.json
Saving config file /home/user/.ft/config.json

It is possible to create as many sandboxes as wanted, the only constraint is for the name to start with sandbox.

Now, let’s start the node:

─➤ ft node start

Config loaded from /home/user/.ft/config.json
Network: sandbox1
Loading wallet file /home/user/.ft/sandbox1/wallet.json
Calling docker start local-node-7081
local-node-7081

It is possible to check that the node is running using:

─➤ ft node web

Config loaded from /home/user/.ft/config.json
Network: sandbox1
Loading wallet file /home/user/.ft/sandbox1/wallet.json
Calling xdg-open http://0.0.0.0:7081/graphql

That command will open the browser on the GraphQL Playground page of the server.

Now, we also have a set of preconfigured users, but we will need some coins to be able to deploy the smart contract:

─➤ ft account info

Config loaded from /home/user/.ft/config.json
Network: sandbox1
Loading wallet file /home/user/.ft/sandbox1/wallet.json
Account "giver": 4_999_999_999.975_350 TONs
Account "user9": not yet created
Account "user8": not yet created
Account "user7": not yet created
Account "user6": not yet created
Account "user5": not yet created
Account "user4": not yet created
Account "user3": not yet created
Account "user2": not yet created
Account "user1": not yet created
Account "user0": not yet created

So, let’s ask the giver for some coins:

╰─➤ ft node give user1

Config loaded from /home/user/.ft/config.json
Network: sandbox1
Loading wallet file /home/user/.ft/sandbox1/wallet.json
For key user1
Generating external inbound message...

MessageId: 71fa31ea4a0c986a1bbbed1e5a0e06b3ab61c2142ac2e5e273d1e386b4ece810
Expire at: unknown
Succeeded.
call returned {}
node url: http://0.0.0.0:7081
Contract deployed at 0:f89872394a383dc289f27ded48f02a6269e19d02be5821ba8081c67a1070588a
Saving wallet file /home/user/.ft/sandbox1/wallet.json
Saving config file /home/user/.ft/config.json

We can now check the account of user1:

╰─➤ ft account info user1

Config loaded from /home/user/.ft/config.json
Network: sandbox1
Loading wallet file /home/user/.ft/sandbox1/wallet.json
Account "user1": 1_000.944_105_999 TONs

(note that we could have used ft account user1 -v to get a complete view of the account, with all other fields)

Now, let’s deploy the contract Basic. For that, we will create an account basic and call the constructor with the argument "Hello World":

─➤ ft contract deploy Basic --create basic --params '{ "arg": "%{hex:string:Hello World}" }'

Config loaded from /home/user/.ft/config.json
Network: sandbox1
Loading wallet file /home/user/.ft/sandbox1/wallet.json

       <--------- key generation for basic ---------->

{ "public": "e017bc6792ee1c6cc6db8b4ee4edb00f2c958814a0f8aa46a7973b9493101c16",
      "secret": "2e25b0c601725e447693b5a3a20c8b7b06da4d0ebb94bb8a4453d33b85a9453e" }
Key for user "basic" generated
Account modified.
{ "name": "basic",
      "passphrase":
  "bus turtle safe isolate surface fee later cream banner buffalo soul mom",
      "pair":
  { "public":
      "e017bc6792ee1c6cc6db8b4ee4edb00f2c958814a0f8aa46a7973b9493101c16",
    "secret":
      "2e25b0c601725e447693b5a3a20c8b7b06da4d0ebb94bb8a4453d33b85a9453e" },
"account":
  { "address":
      "0:8a33da5fb4e623f796a33efeae19374cf8646ad050ebf3aac0c5d6766d76f969",
    "contract": "Basic" } }

       <--------- token transfer to credit account ---------->

Generating external inbound message...

MessageId: 46fb33141f118e7d107f34875a9dc7341c05a4bab58c74248da4d302c29aa2ed
Expire at: unknown
Succeeded.
call returned {
      "transId": "0"
      }

       <--------- contract deployment ---------->

node url: http://0.0.0.0:7081
Contract deployed at 0:8a33da5fb4e623f796a33efeae19374cf8646ad050ebf3aac0c5d6766d76f969
Saving wallet file /home/user/.ft/sandbox1/wallet.json
Saving config file /home/user/.ft/config.json

Here, several things happened:

  1. We used the --create basic option to create a new address for the contract with a key pair, associated with the account name basic (you can use --replace basic to recreate an address, while keeping the same account name, for example if you changed the contract code and rebuilt it before redeploying). When the --create/replace options are used, 1 TON is sent automatically from the deployer account (user1 by default), unless the --sign SOURCE option is specified.
  2. We used an automatic substitution in the argument. Indeed, strings values must be passed as hexa, so we used the notation %{hex:string:STRING} to convert the STRING to hexa. Several substitutions are available, such as {base64:file:FILENAME} to translate the content of a file to base64. See the substitutions section for more.
  3. The contract was deployed and the constructor called with the argument.

We can now call the get_var method:

╰─➤ ft call basic get_var '{}'

Config loaded from /home/user/.ft/config.json
Network: sandbox1
Generating external inbound message...

MessageId: 276b0b4c0b479932f78fa34d4b2e66e5b2f2d91530240190b0409fec940afc31
Expire at: unknown
Succeeded.
call returned {
  "s": "48656c6c6f20576f726c64"
}

Note that it is possible to use the --run argument to run the method locally:

─➤ ft call basic get_var --run

Config loaded from /home/user/.ft/config.json
Network: sandbox1
Generating external inbound message...

MessageId: a69e5049d7c4d44e3773bc245f988e7f929c57aa2a27a6542bcacd156fa7c36e
Expire at: unknown
Running get-method...
Succeeded.
call returned {
  "s": "48656c6c6f20576f726c64"
}