Skip to main content

Payload (data)

Overview

The data field can hold arbitrary data, but for practical purposes, it is normally one of three:

  • a function call,
  • deploy data, or
  • an upgrade call.

We can always give this data in raw form, however, we usually prefer using a proper type system, for safety.

caution

Always use proxies when the target contract ABI is known. A contract proxy is a Rust equivalent of its ABI, and using adds invaluable type safety to your calls.

Using raw data is acceptable only when we are forwarding calls to unknown contracts, for instance in contracts like the multisig, governance of other forwarders.

Diagram

The basic transition diagram for constructing the data field is the one below. It shows the allowed data types and how to get from one to the other.

Notice how the deploy and upgrade calls are further specified by the code source: either explicit code, or the code of another deployed contract.

What the first diagram does not contain are some additional methods that change the values, but not the type. They are:

  • code_metadata (deploy & upgrade only),
  • argument
  • argument_raw.

If we also add them to the diagram we get a more complex version of it:

info

These are diagrams for the raw calls, without proxies. You can find the one involving proxies here

No data

Transactions with no data are classified as simple transfers. These simple transactions can be transferred using:

  • .transfer(): executes simple transfers with a zero gas limit.
    self.tx()
.to(&caller)
.egld_or_single_esdt(&token_identifier, 0, &balance)
.transfer();
  • .transfer_if_not_empty(): it facilitates the transfer of funds with a zero gas limit only if the amount exceeds zero; otherwise, no action is taken.
    self.tx()
.to(ToCaller)
.payment(&token_payment)
.transfer_if_not_empty();

Untyped function call

.raw_call(...) starts a contract call serialised by hand. It is used in proxy functions. It is safe to use proxies instead since manual serialisation is not type-safe.

Argument

.argument(...) serializes the value, but does not enforce type safety. It adds one argument to a function call.

Can be called multiple times, once for each argument.

tx().raw_call("example").argument(&arg1).argument(&arg2)

It is safe to user proxies instead, whenever possible.

Raw arguments

arguments_raw(...) overrides the entire argument buffer. It takes one argument of type ManagedArgBuffer. The arguments need to have been serialized beforehand.

tx().raw_call("example").arguments_raw(&arguments)

Code metadata

.code_metadata() explicitly sets code metadata.

tx().raw_call("example").code_metadata(code_metadata)

Untyped deploy

.raw_deploy() starts a contract deploy call serialised by hand. It is used in proxy deployment functions. It is safe to use proxies instead since manual serialisation is not type-safe.

Deployment calls needs to set:

Argument

Same as for function call arguments.

tx().raw_deploy().argument(&argument)

Raw arguments

Same as for function call raw arguments.

tx().raw_deploy().arguments_raw(&arguments)

Code

.code(...) explicitly sets the deployment code source as bytes.

Argument will normally be a ManagedBuffer, but can be any type that implements trait CodeValue.

tx().raw_deploy().code(code_bytes)

From source

.from_source(...) will instruct the VM to copy the code from another previously deployed contract.

Argument will normally be a ManagedAddress, but can be any type that implements trait FromSourceValue.

tx().raw_deploy().from_source(other_address)

New address

.new_address(...) defines a mock address for the deployed contract (allowed only in testing environments).

tx().raw_deploy().new_address(address)

The example below is a blackbox test for deploy functionality. This call encapsulates a raw_deploy that explicitly sets the deployment code source with "adder.mxsc.json" and the returned address of the deploy with "sc: adder".

fn deploy(&mut self) {
self.world
.tx()
.from(OWNER_ADDRESS)
.raw_deploy()
.argument(5u32)
.code(CODE_PATH)
.new_address(ADDER_ADDRESS)
.run();
}

Untyped upgrade

.raw_upgrade() starts a contract deployment upgrade serialised by hand. It is used in a proxy upgrade call. It is safe to use proxies instead since manual serialisation is not type-safe. All upgrade calls require:

Argument

Same as for function call arguments.

tx().raw_upgrade().argument(&argument)

Raw arguments

Same as for function call raw arguments.

tx().raw_upgrade().arguments_raw(&arguments)

Code metadata

Same as for function call raw arguments.

tx().raw_upgrade().code_metadata(code_metadata)

Code

Same as for deploy code.

Argument will normally be a ManagedBuffer, but can be any type that implements trait CodeValue.

tx().raw_upgrade().code(code_bytes)

From source

Same as for deploy from source.

.from_source(...) will instruct the VM to copy the code from another previously deployed contract.

Argument will normally be a ManagedAddress, but can be any type that implements trait FromSourceValue.

tx().raw_upgrade().from_source(other_address)

The example below is an endpoint that contains upgrade functionality. This call encapsulates a raw_upgrade that explicitly sets the upgrade call source with a specific ManagedAddress and upgradeable code metadata.

#[endpoint]
fn upgrade_from_source(
&self,
child_sc_address: ManagedAddress,
source_address: ManagedAddress,
opt_arg: OptionalValue<ManagedBuffer>,
) {
self.tx()
.to(child_sc_address)
.typed(contract_proxy::ContractProxy)
.upgrade(opt_arg)
.code_metadata(CodeMetadata::UPGRADEABLE)
.from_source(source_address)
.upgrade_async_call_and_exit();
}