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 mycontract {
    uint foo;

    constructor(uint foo_value) {
        foo = foo_value;
    }
}

A constructor does not have a name and may have any number of arguments. If a constructor has arguments, then when the contract is deployed then those arguments must be supplied.

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

Note

Parity Substrate allows multiple constructors to be defined, which is not true for ewasm. So, when building for Substrate, multiple constructors can be defined as long as their argument list is different (i.e. overloaded).

When the contract is deployed in the Polkadot UI, the user can select the constructor to be used.

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.

contact hatchling {
    string name;

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

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

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.

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:

contact hatchling {
    string name;

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

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

The constructor should be declared payable for this to work.

Note

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

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

Note

ewasm does not yet provide a method for setting the salt or gas for the new contract, so these values are ignored.

Note

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

When a new contract is created, the address for the new contract is a hash of the input (the constructor arguments) to the new contract. So, a contract cannot be created twice with the same input. This is why the salt is concatenated to the input. The salt is either a random value or it can be explicitly set using the {salt: 2} syntax. A constant will remove the need for the runtime random generation, however creating a contract twice with the same salt and arguments will fail. The salt is of type uint256.

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

contact hatchling {
    string name;

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

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

When creating contract on Solana, the size of the new account can be specified using space:. By default, the new account is created with a size of 1 kilobyte (1024 bytes) plus the size required for any fixed-size fields. When you specify space, this is the space in addition to the fixed-size fields. So, if you specify space: 0, then there is no space for any dynamicially allocated fields.

contact hatchling {
    string name;

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

contract adult {
    function test() public {
        hatchling h = new hatchling{space: 10240}("luna");
    }
}

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.

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

contact b {
    int foo;
    function func2() public {}
    constructor() {}
}

contact c {
    int 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 availabe 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.

contact a is b {
    constructor() {}
}

contact b is c {
    int foo;
    function func2() public {}
    constructor() {}
}

contact c {
    int 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.

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

contact b {
    function func(int a) virtual public returns (int) {
        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.

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

contact b {
    function func(int a) virtual public returns (int) {
        return a + 10;
    }
}

contact c {
    function func(int a) virtual public returns (int) {
        return a + 5;
    }
}

Calling function in base contract

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

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

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

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
        return a.foo();
    }
}

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;
    }
}

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.

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

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

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

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

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.

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

contact b is c(2) {
    int foo;
    function func2(int i) public {}
    constructor() {}
}

contact c {
    int 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.

contact a is b {
    constructor(int i) b(i+2) {}
}

contact b is c {
    int foo;
    function func2() public {}
    constructor(int j) c(j+3) {}
}

contact c {
    int 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 contact a {
    function func2() virtual public;
}

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 contact a is b {
    constructor() {}
}

contact b {
    constructor(int j) {}
}

contract c is a {
    constructor(int k) b(k*2) {}
}