Download as pdf or txt
Download as pdf or txt
You are on page 1of 10

Solidity File Layout – SPDX License ID and Version

Pragmas
blog.finxter.com/layout-of-a-solidity-source-file-spdx-license-identifier-and-version-pragmas

September 21, 2022

Layout of a Solidity Source File - SPDX License ID and Version Pragmas

In the previous articles, we looked at some of the representative examples of smart


contracts representing possible real-world scenarios.

Our main focus was on capturing the essence of each case, without particular attention
given to the general structure, i.e. layout of the respective source files.

However, in this mini-series starting with this article, we will focus particularly on the
source file layout.

Solidity File LayoutDownload


The articles will continue with our tradition of going hand in hand with the official Solidity
documentation, with the particular topic of our current interest available here.

Info: As we’ve reached such a nice, round number of articles on Solidity, I have a small
foreword for my faithful audience.

For those of us who missed or skipped previous articles, the intent behind the content is
to supplement and clarify the original documentation and even present it in a style that I
find to be more appropriate to us as the audience.

Given that we come from various backgrounds, some less and some more technical, it is
my permanent goal to soften the material and make it as close as possible to each
reader. Sometimes, completely unannounced and unprovoked, I’ll even try and sprinkle
some humor onto the content.

Will I succeed in making it funny and engaging? That’s whole another story

SPDX License Identifier


Smart contracts are somewhat a mystery to unfamiliar folks, and a mystery usually
implies a certain amount of distrust. Even so, the more sensitive the subject is, the
greater the amount of distrust. The best way to turn distrust into trust is to make the
content in question open and available.

When we’re talking about smart contracts, the openness of a smart contract means the
availability of its source code. However, making the source code available frequently
triggers legal problems regarding copyright.

1/4
To alleviate these problems, the Solidity compiler instigates the use of SPDX license
identifiers.

Info: SPDX stands for the Software Package Data Exchange, which is “An open
standard for communicating software bill of material information, including components,
licenses, copyrights, and security references. SPDX reduces redundant work by providing
a common format for companies and communities to share important data, thereby
streamlining and improving compliance.“

Yes, I agree, it’s a lengthy sentence, but the main takeaway ideas are a communication
standard, an instrument of compliance, and a data exchange format:

1. SPDX is a standard used for communicating the information about the software
contents;
2. SPDX reduces redundant work and improves compliance;
3. SPDX provides a common format for data sharing between companies and
communities;

An SPDX license identifier should be included at the beginning of the source file, e.g.

// SPDX-License-Identifier: GPL-3.0-or-later
Although SPDX license identifiers are machine-readable, the compiler does not check if
the license part of the comment is in the list of licenses allowed by SPDX.

Instead, the compiler will just include the string in the bytecode metadata.

We will touch on the subject of contract metadata in future articles, but until then, let’s just
remember that there is a thing called metadata.

Info: Metadata can be loosely defined as “data/information about data”, meaning it


provides more information or description of certain data.

We don’t have to specify a license or if the case is that the source code is closed-source
(opposite of open-source), the recommendation is that we use a special value
UNLICENSED.

The UNLICENSED value implies that usage is not allowed, i.e. there is not a
corresponding item in SPDX license list; it differs from the value UNLICENSE which
grants all rights to everyone.

Solidity documentation authors note that Solidity adheres to the npm recommendation.

If we as developers supply the UNLICENSE comment, we are still tied by the obligation
related to licensing, i.e. we have to mention a specific license header or the original
copyright holder in the source files.

Although the compiler recognizes the comment placed at any location in the source file,
the recommendation, and good practice is to put it at the top of the file.

2/4
Pragmas
We’ve mentioned the pragma keyword somewhere in the first few articles, but now we’ll
use the opportunity to say a few more words about it.

Pragma keyword is the element of the Solidity programming language that enables
specific Solidity compiler (remember solc) features or validations, i.e. checks.

As the pragma keyword scope is its source file, we’d have to add the pragma to all our
files to enable it in our whole project.

Note: A pragma from an imported file does not apply to the importer file, i.e. the file that
imports the imported file.

Version Pragma
We always use a version pragma for limiting the source file(s) compilation to a specific
range of compiler versions.

The intention behind this step is the prevention of incompatible changes introduced with
future versions of compilers.

According to the Solidity authors, occurrences of incompatible changes are reduced to an


absolute minimum, meaning that in all other cases i.e. cases of compatible changes, the
changes in Solidity language semantics visibly coincide with the changes in language
syntax.

To stay on the safe side, the recommendation is to study the changelog at least for
releases that carry breaking changes, marked x.0.0 (major releases) or 0.x.0 (minor
releases).

Info: semantic is relating to meaning in language or logic.

As in every our example so far, we’re using the version pragma as:

Note: pragma solidity ^0.x.y; allows changes that do not modify the left-most non-
zero digit in the [major, minor, patch] tuple (docs).

The following line instructs the compilation process to use a compiler with the lowest
version of 0.5.2 and with the highest version not exceeding 0.6.0 (this condition is
incorporated by a ^ symbol):

pragma solidity ^0.5.2;


By recalling the article about semantic versioning, we’ll remember that no breaking
changes are introduced until a minor version of 0.6.0 (in this specific case), therefore we
can be sure that our code will compile just as we expect it to.

3/4
Also, by using the line above, we didn’t lock on the specific version, so the last part of the
version label, i.e. the patch number can increase, leaving enough space for the compiler
bug fixes.

Besides this most common way of expressing the allowed versions of the compiler, even
more, complex rules are available by using the syntax available here.

Note: version pragma just instructs the compiler to self-check if it is compliant with the
version required by the source file. In case of a mismatch, the compiler will throw an
(in)appropriate error. I mean, who ever saw an appropriate error, anyways?

Conclusion
With this introductory article to the topic of the layout of a Solidity source file, we covered
a few very light concepts, including SPDX license identifier, reintroduced the pragma
keyword, and retouched the version pragma.

In the next article, we will continue with the next two pragmas and other, very interesting
topics.

In the SPDX License Identifier section, we were asking around inconspicuously about the
SPDX. We wanted to find out what it is, how and when it is used, and how it can make
our developing life easier.

In the Pragmas section, we proudly reminded ourselves of the knowledge from long ago,
why do we have to slam a pragma at the beginning of each source file?

If at least they looked nice… Starting our coding masterpieces with a dangling comment
seemed like a skewed joke (like most of mine do) – until we learned why

What’s Next?
This tutorial is part of our extended Solidity documentation with videos and more
accessible examples and explanations. You can navigate the series here (all links open in
a new tab):

Prev Tutorial
Syllabus
Next Tutorial

4/4
Solidity Layout – Pragmas, Importing, and Comments
blog.finxter.com/solidity-layout-pragmas-importing-and-comments

September 25, 2022

Solidity Layout - Pragmas, Importing, and Comments

Following up on the last article, we will take a closer look at other types of pragmas, but
also check out how we can import other source files and paths (docs).

After that, we’ll take a break by going through a small section on comments and getting to
know them better. It’ll probably be very familiar territory for the experienced ones among
us, but it’ll also prove as a useful first encounter for beginners who are yet to become
masters of the programming art.

I like to consider programming as a form of art, because, aside from technical knowledge
and approaches we apply while programming, there’s also a large space waiting to be
filled with creativity and imagination. On that note, let’s roll

ABI Coder Pragma


First of all, let’s explain what is ABI, and then what are ABI encoder and ABI decoder.

According to Wikipedia, an ABI is a shorthand for an Application Binary Interface, which is


“an interface between two binary program modules. Most often, one of these modules is a
library or operating system facility, and the other is a program that is being run by a user.”

Not a very complicated definition, but let’s elaborate on it in more detail.

An ABI allows us, i.e. our smart contracts to access the data structures and functions of
another smart contract, which runs as machine code, i.e. bytecode on an EVM.

We know that bytecode is a product of the final phase of the compilation process, and it
runs very low, directly on the underlying hardware.

This means that the ABI definition must provide us with instructions or mapping on how to
transform our high-level language access to data structures and functions to
communicate with an EVM running the bytecode, and also reinterpret the EVM’s
response in bytes back to a high-level language data structure.

The process of translating high-level language calls to bytes and from bytes is called ABI
encoding and ABI decoding. ABI encoding and decoding are mostly done automatically,
by the compilers or wallets, which interact with the blockchain (source).

Info: For comparison, but on a different and higher level, an API as a shorthand for
Application Programming Interface defines equivalent ways of accessing resources
through a source code.

1/6
We can select one of the two implementations by stating pragma abicoder v1 or pragma
abicoder v2.

An important difference is that the ABI coder v2 can encode and decode arbitrarily nested
arrays and structs.

A “price” for that is possibly less optimal code, and it has not been as thoroughly tested
as the older version. However, it is not considered to be an experimental version since
Solidity v0.6.0. To use ABI coder v2 before Solidity v0.8.0, we have to explicitly select it
using the corresponding pragma abicoder v2;, otherwise, ABI coder v1 will be used by
default.

The situation changed with Solidity v0.8.0 when ABI coder v2 became the default, and
ABI coder v1 became an option by stating pragma abicoder v1;.

The new encoder (v2) supports all the same types as the old one (v1), so the contracts
that use the new coder can seamlessly interact with the contracts that use the old coder.

Communication in the other direction is also possible, but only if contracts that use the old
coder avoid the calls that are only supported by the new coder. When that’s not the case,
the Solidity compiler will show an error, but we can easily fix that (and get rid of the error)
by switching to the new coder.

Note: The ABI coder pragma is applied to the entire source code file. This means that if
we use the old coder in our contract and inherit a code from a contract that uses the new
coder, the inherited code can be freely used in our contract, as long as the new types are
used internally, but not in external function signatures.

Info: A function signature consists of the function name, function parameters, and
sometimes, a return type.

Experimental Pragma
Experimental pragma lets us activate features of a compiler or the Solidity language that
are initially disabled. A notable, still experimental pragma is the one regarding the
SMTChecker component: pragma experimental SMTChecker;

SMTChecker
We’ll surely remember the article about the Solidity compiler installation, it was quite a
lengthy one.

There was an optional component for an installation called SMTChecker. SMTChecker


may be included in the resulting compilation output when the Solidity compiler is being
compiled, i.e. built.

2/6
Regarding the available downloads, some static binaries incorporate SMTChecker, and
also some don’t have it included. Following the official documentation, it is available in
most static binary versions for the Ubuntu PPA releases (Ubuntu Personal Package
Archives).

The Docker images, Windows binaries, or the statically built Linux binaries mainly don’t
include SMTChecker. Solidity compiler for Node.js, solc-js can activate SMTChecker via
the smtCallback in case we have an SMT solver installed in our development
environment and can run solc-js via the node command.

An example shows how to use SMTChecker via smtSolver callback (GitHub):

var solc = require('solc');


const smtchecker = require('solc/smtchecker');
const smtsolver = require('solc/smtsolver');
// Note that this example only works via node and not in the browser.
var input = {
language: 'Solidity',
sources: {
'test.sol': {
content: 'contract C { function f(uint x) public { assert(x > 0); } }'
}
},
settings: {
modelChecker: {
engine: "chc",
solvers: [ "smtlib2" ]
}
}
};
var output = JSON.parse(
solc.compile(
JSON.stringify(input),
{ smtSolver: smtchecker.smtCallback(smtsolver.smtSolver, smtsolver.availableSolvers[0])
}
)
);
Note: “Using formal verification it is possible to perform an automated mathematical proof
that your source code fulfills a certain formal specification. The specification is still formal
(just as the source code), but usually much simpler.” (docs).

This was an excerpt from the SMTChecker and Formal Verification section, Solidity
documentation.

3/6
By using SMTChecker, we can start getting safety warnings internally produced by an SMT
solver. We have to have in mind that the SMT solver isn’t yet completely aligned with all
features of the Solidity language, and may report a noticeable amount of warnings,
stemming from yet unsupported features.

Importing other Source Files


We can organize the source code in Solidity by using import statements. They are similar
to the ones JavaScript ES6 and later versions use, but with the notable exclusion of the
default export concept, which is not present in Solidity (source).

A general form of an import statement is import <filename>;, where <filename> is a


placeholder for a file containing some useful code e.g. a library.

An import statement in this form would import all global symbols defined in the imported
file, into the global scope.

This behavior also encompasses the symbols that are imported in the imported file itself.
Due to this reason, it is not recommended to import the entire path because it will fill our
namespace with lots of different objects, both those that we need and also those we don’t
need.

Besides that, when new items are imported into the imported file, they are automatically
propagated to all the namespaces below them.

The alternative forms with the same effect are:

import * as symbolName from "filename";


and

import "filename" as symbolName;


We would follow a better approach by selectively importing symbols, and renaming them
when a need arises:

import {symbol1 as alias, symbol2} from "filename";

Import paths
Solidity compiler aims to support consistent, reproducible builds on all sensible platforms,
and this requires abstracting away the filesystem-specific details about the locations
where source files are stored.

This support is implemented by making the import paths use indirect references to the
files: source units.

The compiler has an internal database, namely a virtual filesystem, VFS. The VFS
holds a record for each source file, a unique source unit name. The import statement (as
shown in previous examples) is translated into a source unit name, which is then used to

4/6
locate the source unit.

Official Solidity Docs: “the recommended way to interface with the Solidity compiler
especially for more complex and automated setups is the so-called JSON-input-output
interface. The same interface is provided by all distributions of the compiler.”

This interface is called Standard JSON API, and by using it, we can directly provide the
names and content of all our source units, i.e. source file paths as the compiler input.

If we want our compiler to automatically find and load source files into the VFS, the
source file names have to be declared in a predefined way so that the import callback can
locate and load them.

We’ll get back to this subject in an interesting future article on the topic of Import Path
Resolution / Virtual File System.

In that context, the command-line compiler supports loading code only from the local
filesystem, so the source units will represent file paths. However, some environments are
more versatile in providing custom callbacks, like the Remix IDE which lets us “import
files from HTTP, IPFS, and Swarm URLs or refer directly to packages in NPM registry”.

Comments
Solidity supports two common types of comments: the single-line comments //, multi-line
comments /* ... */, and the third, special type called the NatSpec comment written as
a triple slash /// or double asterisk block /** ... **/. The NatSpec comments must be
placed just above the function declaration or code statement.

Conclusion
In this article, we learned about or even just reminded ourselves of several neat features
of the Solidity programming language.

We continued on the previous article with two more pragma usages and stumbled on a
short story about importing other source files.

Then, we stepped into a topic about importing paths and concluded with a dash on
comments.

First, we clarified (I hope not the opposite) what ABI is and how ABI coder pragma
works.
Second, we met face to face with an experimental pragma, namely the
SMTChecker
Third, we discussed what it means to import from other source files.
Fourth, we continued our importer’s career and started importing paths.
Fifth, we played a short game with comments.

5/6
What’s Next?
This tutorial is part of our extended Solidity documentation with videos and more
accessible examples and explanations. You can navigate the series here (all links open in
a new tab):

Prev Tutorial
Syllabus
Next Tutorial

6/6

You might also like