Contracts

Constructors and contract instantiation

When a contract is deployed, the contract storage is initialized to the initializer values provided, and any constructor is called. A constructor is not required for a contract. A constructor is defined like so:

contract flipper {
        bool private value;

        /// Constructor that initializes the `bool` value to the given `init_value`.
        constructor(bool initvalue) {
                value = initvalue;
        }

        /// A message that can be called on instantiated contracts.
        /// This one flips the value of the stored `bool` from `true`
        /// to `false` and vice versa.
        function flip() public {
                value = !value;
        }

        /// Simply returns the current value of our `bool`.
        function get() public view returns (bool) {
                return value;
        }
}

A constructor can have any number of arguments. If a constructor has arguments, they must be supplied when the contract is deployed.

If a contract is expected to receive value on instantiation, the constructor should be declared payable.

Note

Solang allows naming constructors in the Polkadot target:

abstract contract Foo {
    constructor my_new_foo() {}
}

Constructors without a name will be called new in the metadata.

Note that constructor names are only used in the generated metadata. For contract instantiation, the correct constructor matching the function signature will be selected automatically.

Instantiation using new

Contracts can be created using the new keyword. The contract that is being created might have constructor arguments, which need to be provided. While on Polkadot and Ethereum constructors return the address of the instantiated contract, on Solana, the address is either passed to the call using the {program_id: ...} call argument or is declared above a contract with the @program_id annotation. As the constructor does not return anything and its purpose is only to initialize the data account, the syntax new Contract()``is not idiomatic on Solana. Instead, a function ``new is made available to call the constructor.

contract hatchling {
    string name;
    address private origin;

    constructor(string id, address parent) {
        require(id != "", "name must be provided");
        name = id;
        origin = parent;
    }

    function root() public returns (address) {
        return origin;
    }
}

contract adult {
    function test() public {
        hatchling h = new hatchling("luna", address(this));
    }
}

The constructor might fail for various reasons, for example require() might fail here. This can be handled using the Try Catch Statement statement, else errors cause the transaction to fail.

Note

On Solana, the Try Catch Statement statement is not supported, as any failure will cause the entire transaction to fail.

Sending value to the new contract

It is possible to send value to the new contract. This can be done with the {value: 500} syntax, like so:

contract hatchling {
    string name;
    address private origin;

    constructor(string id, address parent) payable {
        require(id != "", "name must be provided");
        name = id;
        origin = parent;
    }

    function root() public returns (address) {
        return origin;
    }
}

contract adult {
    function test() public {
        hatchling h = new hatchling{value: 500}("luna", address(this));
    }
}

The constructor should be declared payable for this to work.

Note

If no value is specified, then on Polkadot the minimum balance (also know as the existential deposit) is sent.

Note

On Solana, this functionality is not available.

Setting the salt, gas, and address for the new contract

Note

The gas or salt cannot be set on Solana. However, when creating a contract on Solana, the address of the new account must be set using address:.

When a new contract is created, the address for the new contract is a hash of the input (the encoded constructor arguments) to the new contract and the salt. A contract cannot be created twice with the same input and salt. By giving a different salt, the same input can be used twice for a new contract. The salt can be set using the {salt: hex"439d399ee3b5b0fae6c8d567a8cbfa22d59f8f2c5fe308fd0a92366c116e5f1a"} syntax, or if it is omitted, then a random value is used.

Specifying a salt will remove the need for generating a random value at runtime, however care must be taken to avoid using the same salt more than once. Creating a contract twice with the same salt and arguments will fail. The salt is of type bytes32.

If gas is specified, this limits the amount gas the constructor for the new contract can use. gas is a uint64.

contract hatchling {
    string name;
    address private origin;

    constructor(string id, address parent) {
        require(id != "", "name must be provided");
        name = id;
        origin = parent;
    }

    function root() public returns (address) {
        return origin;
    }
}

contract adult {
    function test() public {
        hatchling h = new hatchling{salt: 0, gas: 10000}("luna", address(this));
    }
}

Solana constructors

Solidity contracts are coupled to a data account, which stores the contract’s state variables on the blockchain. This account must be initialized before calling other contract functions, if they require one. A contract constructor initializes the data account and can be called with the new function. When invoking the constructor from another contract, the data account to initialize appears in the IDL file and is identified as contractName_dataAccount. In the example below, the IDL for the instruction test requires the hatchling_dataAccount account to be initialized as the new contract’s data account.

contract hatchling {
    string name;

    constructor(string id) payable {
        require(id != "", "name must be provided");
        name = id;
    }
}

contract adult {
    function test(address id) external {
        hatchling.new{program_id: id}("luna");
    }
}

When there are no call arguments to a constructor call, the compiler will automatically create the AccountMeta array the constructor call needs. Due to the impossibility to track account ordering in private, internal and public functions, such a call argument is only allowed in functions with external visibility. This automatic account management only works, however, if there is a single instantiation of a particular contract type.

Alternatively, the data account to be initialized can be provided using the accounts call argument. In this case, one needs to instantiate a fixed length array of type AccountMeta to pass to the call. The array must contain all the accounts the transaction is going to need, in addition to the data account to be initialized.

For the creation of a contract, the data account must the first element in such a vector and the system account 11111111111111111111111111111111 must also be present. If the constructor one is calling has the @payer annotation, the payer account should appear in the array as well. Moreover, the is_signer and is_writable bool flags need to be properly set, according to the following example:

import 'solana';

contract creator {

    @mutableSigner(data_account_to_initialize)
    @mutableSigner(payer)
    function create_with_metas() external {
        AccountMeta[3] metas = [
            AccountMeta({
                pubkey: tx.accounts.data_account_to_initialize.key,
                is_signer: true,
                is_writable: true}),
            AccountMeta({
                pubkey: tx.accounts.payer.key,
                is_signer: true,
                is_writable: true}),
            AccountMeta({
                pubkey: address"11111111111111111111111111111111",
                is_writable: false,
                is_signer: false})
        ];

        Child.new{accounts: metas}();

        Child.use_metas{accounts: []}();
    }
}

@program_id("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT")
contract Child {
    @payer(payer)
    constructor() {
        print("In child constructor");
    }

    function use_metas() pure public {
        print("I am using metas");
    }
}

The sequence of the accounts in the AccountMeta array matters and must follow the IDL ordering.

Calling a contract on Solana

A call to a contract on Solana follows a different syntax than that of Solidity on Ethereum or Polkadot. As contracts cannot be a variable, calling a contract’s function follows the syntax Contract.function(). If the contract definition contains the @program_id annotation, the CPI will be directed to the address declared inside the annotation.

If that annotation is not present, the program address must be manually specified with the {program_id: ... } call argument. When both the annotation and the call argument are present, the compiler will forward the call to the address specified in the call argument.

contract Polymath {
    function call_math() external returns (uint) {
        return Math.sum(1, 2);
    }
    function call_english(address english_id) external returns (string) {
        return English.concatenate{program_id: english_id}("Hello", "world");
    }
}

@program_id("5afzkvPkrshqu4onwBCsJccb1swrt4JdAjnpzK8N4BzZ")
contract Math {
    function sum(uint a, uint b) external returns (uint) {
        return a + b;
    }
}

contract English {
    function concatenate(string a, string b) external returns (string) {
        return string.concat(a, b);
    }
}

Base contracts, abstract contracts and interfaces

Solidity contracts support object-oriented programming. The style Solidity is somewhat similar to C++, but there are many differences. In Solidity we are dealing with contracts, not classes.

Specifying base contracts

To inherit from another contract, you have to specify it as a base contract. Multiple contracts can be specified here.

contract a is b, c {
    constructor() {}
}

contract b {
    int256 foo;

    function func2() public {}

    constructor() {}
}

contract c {
    int256 bar;

    constructor() {}

    function func1() public {}
}

In this case, contract a inherits from both b and c. Both func1() and func2() are visible in contract a, and will be part of its public interface if they are declared public or external. In addition, the contract storage variables foo and bar are also available in a.

Inheriting contracts is recursive; this means that if you inherit a contract, you also inherit everything that that contract inherits. In this example, contract a inherits b directly, and inherits c through b. This means that contract b also has a variable bar.

contract a is b {
    constructor() {}
}

contract b is c {
    int256 foo;

    function func2() public {}

    constructor() {}
}

contract c {
    int256 bar;

    constructor() {}

    function func1() public {}
}

Virtual Functions

When inheriting from a base contract, it is possible to override a function with a newer function with the same name. For this to be possible, the base contract must have specified the function as virtual. The inheriting contract must then specify the same function with the same name, arguments and return values, and add the override keyword.

contract a is b {
    function func(int256 a) public override returns (int256) {
        return a + 11;
    }
}

contract b {
    function func(int256 a) public virtual returns (int256) {
        return a + 10;
    }
}

If the function is present in more than one base contract, the override attribute must list all the base contracts it is overriding.

contract a is b, c {
    function func(int256 a) public override(b, c) returns (int256) {
        return a + 11;
    }
}

contract b {
    function func(int256 a) public virtual returns (int256) {
        return a + 10;
    }
}

contract c {
    function func(int256 a) public virtual returns (int256) {
        return a + 5;
    }
}

Calling function in base contract

When a virtual function is called, the dispatch is virtual. If the function being called is overridden in another contract, then the overriding function is called. For example:

abstract contract a {
    function foo() internal virtual returns (uint64) {
        return 1;
    }

    function bar() internal returns (uint64) {
        // since foo() is virtual, is a virtual dispatch call
        // when foo is called and a is a base contract of b, then foo in contract b will
        // be called; foo will return 2.
        return foo();
    }

    function bar2() internal returns (uint64) {
        // this explicitly says "call foo of base contract a", and dispatch is not virtual
        // however, if the call is written as a.foo{program_id: id_var}(), this represents
        // an external call to contract 'a' on Solana.
        return a.foo();
    }
}

contract b is a {
    function baz() public pure returns (uint64) {
        return foo();
    }

    function foo() internal pure override returns (uint64) {
        return 2;
    }
}

Rather than specifying the base contract, use super as the contract to call the base contract function.

contract a is b {
    function baz() public returns (uint64) {
        // this will return 1
        return super.foo();
    }

    function foo() internal override returns (uint64) {
        return 2;
    }
}

abstract contract b {
    function foo() internal virtual returns (uint64) {
        return 1;
    }
}

If there are multiple base contracts which the define the same function, the function of the first base contract is called.

abstract contract b1 {
    function foo() internal virtual returns (uint64) {
        return 100;
    }
}

abstract contract b2 {
    function foo() internal virtual returns (uint64) {
        return 200;
    }
}

contract a is b1, b2 {
    function baz() public returns (uint64) {
        // this will return 100
        return super.foo();
    }

    function foo() internal override(b1, b2) returns (uint64) {
        return 2;
    }
}

Specifying constructor arguments

If a contract inherits another contract, then when it is instantiated or deployed, then the constructor for its inherited contracts is called. The constructor arguments can be specified on the base contract itself.

contract a is b(1) {
    constructor() {}
}

contract b is c(2) {
    int256 foo;

    function func2(int256 i) public {}

    constructor(int256 j) {}
}

contract c {
    int256 bar;

    constructor(int32 j) {}

    function func1() public {}
}

When a is deployed, the constructor for c is executed first, then b, and lastly a. When the constructor arguments are specified on the base contract, the values must be constant. It is possible to specify the base arguments on the constructor for inheriting contract. Now we have access to the constructor arguments, which means we can have runtime-defined arguments to the inheriting constructors.

contract a is b {
    constructor(int256 i) b(i + 2) {}
}

contract b is c {
    int256 foo;

    function func2() public {}

    constructor(int256 j) c(int32(j + 3)) {}
}

contract c {
    int256 bar;

    constructor(int32 k) {}

    function func1() public {}
}

The execution is not entirely intuitive in this case. When contract a is deployed with an int argument of 10, then first the constructor argument or contract b is calculated: 10+2, and that value is used as an argument to constructor b. constructor b calculates the arguments for constructor c to be: 12+3. Now, with all the arguments for all the constructors established, constructor c is executed with argument 15, then constructor b with argument 12, and lastly constructor a with the original argument 10.

Abstract Contracts

An abstract contract is one that cannot be instantiated, but it can be used as a base for another contract, which can be instantiated. A contract can be abstract because the functions it defines do not have a body, for example:

abstract contract a {
    function func2() public virtual;
}

This contract cannot be instantiated, since there is no body or implementation for func2. Another contract can define this contract as a base contract and override func2 with a body.

Another reason why a contract must be abstract is missing constructor arguments. In this case, if we were to instantiate contract a we would not know what the constructor arguments to its base b would have to be. Note that contract c does inherit from a and can specify the arguments for b on its constructor, even though c does not directly inherit b (but does indirectly).

abstract contract a is b {
    constructor() {}
}

contract b {
    int256 public j;

    constructor(int256 _j) {}
}

contract c is a {
    int256 public k;

    constructor(int256 k) b(k * 2) {}
}