title: Polymorphism in Solidity
date: July 31, 16:44
---
<p>
Solidity is in many ways similar to C. It's a low-level language, sitting just a
thin layer of abstraction above its underlying bytecode. Just like C, it lacks
some convenient mechanisms from higher level languages.
</p>

<p>
There's a cool method for implementing simple object-orientation (complete with
polymorphism) in C that can also be applied in Solidity to solve similar
problems.
</p>

<p>
Let's look at how Linux kernel programmers deal with filesystems.
</p>

<p>
Each filesystem needs its own low-level implementation. At the same time, it
would be nice to have the abstract concept of a <em>file</em>, and be able to
write generic code that can interact with <em>files</em> living on any sort of
filesystem. Sounds like polymorphism.
</p>

<p>
Here's the first few lines of the definition of
<a href='https://elixir.bootlin.com/linux/v5.2.4/source/include/linux/fs.h#L922'>
    <code>struct file</code>
</a>, used
to keep track of a file in the Linux kernel.

<pre>
struct file {
    struct path                     f_path;
    struct inode                    *f_inode;
    <b>const struct file_operations    *f_op</b>;
    // ...
};
</pre>
</a>

<p>
The important bit is the <code>f_op</code> field, of type <code>struct
    file_operations</code>. Let's
look at (an abridged version of)
<a href='https://elixir.bootlin.com/linux/v5.2.4/source/include/linux/fs.h#L1791'>
    <em>its</em> definition
</a>.

<pre>
struct file_operations {
    struct module *owner;
    loff_t (*<b>llseek</b>) (struct file *, loff_t, int);
    ssize_t (*<b>read</b>) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*<b>write</b>) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*<b>read_iter</b>) (struct kiocb *, struct iov_iter *);
    ssize_t (*<b>write_iter</b>) (struct kiocb *, struct iov_iter *);
    int (*<b>mmap</b>) (struct file *, struct vm_area_struct *);
    int (*<b>open</b>) (struct inode *, struct file *);
    // ...
};
</pre>
</a>

It's mostly a bunch of function pointers that appear to be... operations one
might want to perform on a file.
</p>

<p>
To emulate OO, inside our "object" (<code>struct file</code>) we manually store
a container for its "methods" (<code>struct file_operations</code>). Each of
these, as its first argument, takes a pointer to a <code>struct file</code> that
it's going to operate on.
</p>

<p>
With this in place, we can now define a generic
<a href='https://elixir.bootlin.com/linux/v5.2.4/source/fs/read_write.c#L421'>
    <code>read</code>
</a> system call:

<pre>
ssize_t __vfs_read(struct file *file, char __user *buf, size_t count,
           loff_t *pos)
{
    if (<b>file-&gt;f_op-&gt;read</b>)
        return <b>file-&gt;f_op-&gt;read(</b>file, buf, count, pos<b>)</b>;
    else if (<b>file-&gt;f_op-&gt;read_iter</b>)
        return new_sync_read(file, buf, count, pos);
    else
        return -EINVAL;
}
</pre>

On the other hand, the file
<a href='https://elixir.bootlin.com/linux/v5.2.4/source/fs/ext4/file.c#L508'>
    <code>fs/ext4/file.c</code>
</a> defines operations specific to the ext4 file system and a
<code>file_operations</code> struct:

<pre>
const struct file_operations ext4_file_operations = {
    .llseek     = ext4_llseek,
    .read_iter  = ext4_file_read_iter,
    .write_iter = ext4_file_write_iter,
    // ...
};
</pre>
</p>

<p>
We can do the same thing in Solidity!
</p>

<p>
The example we'll work with is a decentralized exchange. Users can call a
<code>trade</code> function with the following signature:

<pre>
function trade(address sellCurrency, address buyCurrency, uint256 sellAmount);
</pre>

They specify a currency pair (<code>sellCurrency</code> and <code>buyCurrency</code> -
the currency they're selling to and buying from the exchange) and the amount of
<code>sellCurrency</code> they're giving to the exchange. The smart contract then
calculates the amount of <code>buyCurrency</code> the user should receive and
transfers that to them.
</p>

<p>
To compilcate things a little, let's say that the exchange deals with more than
just ERC20 tokens. Let's allow for ERC20 - ERC20, ERC20 - Ether, and Ether -
ERC20 trades.
</p>

<p>
Here's what a first attempt at implementing this might look like:

<pre>
// Let address(0) denote Ether
function trade(address sellCurrency, address buyCurrency, uint256 sellAmount) {
  uint256 buyAmount = calculateBuyAmount(sellCurrency, buyCurrency, sellAmount);

  // <b>take the user's sellCurrency</b>
  if (sellCurrency == address(0)) {
    require(msg.value == sellAmount);
  } else {
    ERC20(sellCurrency).transferFrom(msg.sender, address(this), sellAmount);
  }

  // <b>give the user their new buyCurrency</b>
  if (buyCurrency == address(0)) {
    msg.sender.transfer(buyAmount);
  } else {
    ERC20(buyCurrency).transfer(msg.sender, buyAmount);
  }
}
</pre>
</p>

<p>
This doesn't look terrible yet.
</p>

<p>
Now imagine that you wanted to handle even more asset classes.

<p>
What if there was a token <code>YourToken</code> that had <code>mint</code> and
<code>burn</code> functions callable by the exchange contract? Instead of
holding a balance of <code>YourToken</code> you just want to either take tokens
out of ciruclation when they're sold, or mint new ones into existence when
they're bought.
</p>

<p>
Or you want to support <code>MyToken</code> which I annoyingly implemented
without following the ERC20 standard and function names differ from other tokens.
</p>

<p>
With more and more asset classes, the complexity of the code above would
increase.
</p>

<p>
Now let's try to implement the same logic but taking inspiration from the Linux
kernel's generic handling of files.
</p>

<p>
First, let's declare the struct that will hold a currency's information and methods
for interacting with it. This corresponds to <code>struct file</code>:

<pre>
struct Currency {
  function (Currency, uint256) take;
  function (Currency, uint256) give;
  address currencyAddress;
}
</pre>
</p>

<p>
Now let's implement taking and giving tokens for two different asset classes.

<pre>
function <b>ethTake</b>(Currency currencyS, uint256 amount) {
  require(msg.value == sellAmount);
}

function <b>ethGive</b>(Currency currencyS, uint256 amount) {
  msg.sender.transfer(buyAmount);
}

function <b>erc20Take</b>(Currency currencyS, uint256 amount) {
  ERC20 token = ERC20(currencyS.currencyAddress);
  token.transferFrom(msg.sender, address(this), amount);
}

function <b>erc20Give</b>(Currency currencyS, uint256 amount) {
  ERC20 token = ERC20(currencyS.currencyAddress);
  token.transfer(msg.sender, amount);
}
</pre>
</p>

<p>
Finally, we can perform generic operations on currencies:

<pre>
function trade(Currency sellCurrency, Currency buyCurrency, uint256 sellAmount
) {
  uint256 buyAmount = calculateBuyAmount(sellCurrency, buyCurrency, sellAmount);

  <b>sellCurrency.take(</b>sellCurrency, sellAmount<b>)</b>;
  <b>buyCurrency.give(</b>buyCurrency, buyAmount<b>)</b>;
}
</pre>
</p>

<p>
Adding support for a new asset class is now as simple as defining a pair of take/give
functions. The code inside of <code>trade</code> need never be touched again,
following the Open/Closed principle.
</p>