Skip to main content

Transpiling solidity smart contract with Sol2ink

Probably you heard about advantages WASM smart contracts and if you are working with EVM you may be interested in how to rewrite your project written with solidity to Rust and ink!

Installation

First what you need is, of course, Sol2ink tool. You can use binary from release. Move that file to folder that contains solidity smart contract and use ./sol2ink Contract.sol command. Or you can clone repository and work inside Sol2ink project. We will work in this way.

Transpiling

Take a look on code below:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;


contract Owner {

address private owner;

event OwnerSet(address indexed oldOwner, address indexed newOwner);

constructor() {
console.log("Owner contract deployed by:", msg.sender);
owner = msg.sender;
emit OwnerSet(address(0), owner);
}

function changeOwner(address newOwner) public {
emit OwnerSet(owner, newOwner);
owner = newOwner;
}

function getOwner() external view returns (address) {
return owner;
}
}

It's simple solidity contract that stores owner's address, allows to change this address and return it. To transpile it, we need to run a command

cargo +nightly run Contract.sol

After this, you can see that generated folder has been created. Here you can find contract and src folder. More about structure you can read here.

Add new logic

If you want to add extra functionality to your generated project, you need go to src folder and work with traits and impls folders. Let's add to our contract function for reset owner role. It will set owner to zero address and will emit event after. In traits/contract.rs you have trait definition. We can declare new method here:

#[openbrush::trait_definition]
pub trait Owner {
#[ink(message)]
fn change_owner(&mut self, new_owner: AccountId) -> Result<(), Error>;

#[ink(message)]
fn get_owner(&self) -> Result<AccountId, Error>;

// new method
#[ink(message)]
fn renounce_ownership(&mut self) -> Result<(), Error>;
}

We need use mut because we will change storage value owner. Also, this function have message attribute as it's public.

Our next step is to write implementation for this function and for renounce event. For it we need to change trait implementation in impls/contract.rs and add this function:

fn renounce_ownership(&mut self) -> Result<(), Error> {
let old_owner = self.data().owner.clone();
self.data().owner = ZERO_ADDRESS.into();
self._emit_renounce_ownership_event(old_owner);
Ok(())
}

For now _emit_renounce_ownership_event doesn't exist, so let's create it. You can see that impls/contract.rs have one more trait implementation. We use Internal trait for event declaration. Final Internal trait code will have default event implementation:

pub trait Internal {
fn _emit_owner_set(&self, old_owner: AccountId, new_owner: AccountId);

fn _emit_renounce_ownership_event(&self, old_owner: AccountId);

}

impl<T: Storage<Data>> Internal for T {
default fn _emit_owner_set(&self, _: AccountId, _: AccountId) {}

default fn _emit_renounce_ownership_event(&self, _: AccountId) {}
}

If you want to override event default behavior, you can open contracts/contract/lib.rs and add some logic in Internal implementation for Contract:

fn _emit_renounce_ownership_event(&self, old_owner: AccountId) {
self.env().emit_event(ContractSet {
old_owner,
ZERO_ADDRESS,
});
}

We’re done! Now you know how to transpile contracts with Sol2ink, what project structure it uses and how work with project after transpiling.

If you have question or something went wrong, feel free to share it in our discord.