1. Basic Smart Contract

Part 1 of 3

In this chapter, we will build a simple Ethereum contract that allows us to do three things: deposit BTC, withdraw BTC and check how much BTC we have deposited.

To get started, head over to the online Remix IDE at https://remix.ethereum.org, choose "Solidity" and create a new file called Basic.sol:

Basic.sol
pragma solidity >=0.5.0;
contract Basic {
}

Shifters

In order to accept BTC in our Ethereum contract, we need to talk to the BTCShifter contract. The BTCShifter contract works with RenVM to shift BTC into, and out of, an ERC20-compatible token called zBTC.

All digital assets (BTC, ZEC, etc.) have their own Shifter contract, so we need to ask the ShifterRegistry contract for the address of the BTCShifter.

Let's add some code to our Ethereum contract so that we can talk to the Shifter,ShifterRegistry, and ERC20 contracts:

Basic.sol
pragma solidity >=0.5.0;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
}
interface IShifter {
function shiftIn(bytes32 _pHash, uint256 _amount, bytes32 _nHash, bytes calldata _sig) external returns (uint256);
function shiftOut(bytes calldata _to, uint256 _amount) external returns (uint256);
}
interface IShifterRegistry {
function getShifterBySymbol(string calldata _tokenSymbol) external view returns (IShifter);
function getTokenBySymbol(string calldata _tokenSymbol) external view returns (IERC20);
}
contract Basic {
IShifterRegistry public registry;
constructor(IShifterRegistry _registry) public {
registry = _registry;
}
}

You can find the source code for Shifter and ShifterRegistry here: https://github.com/renproject/darknode-sol/tree/master/contracts/Shifter

Deposit function

We will use the deposit function to shift BTC into Ethereum. When shifting BTC into Ethereum, RenVM will always give us three parameters that we need to forward to the BTCShifter contract:

  • amount represents the amount of BTC we are shifting into Ethereum,

  • nHash (also known as the nonce hash) is used to uniquely identify a shift into Ethereum, and

  • sig is a signature from RenVM to approve the shift.

In addition to these required fields, our Ethereum contract will also allow the user to attach a message to their deposits and withdrawals. Our Ethereum contract will log these messages as Ethereum events (this is not necessary to shift BTC into Ethereum, we are just doing it to demonstrate that you can define extra data and functionality).

Add these logs to our Ethereum contract before the constructor:

event Deposit(uint256 _amount, bytes _msg);
event Withdrawal(bytes _to, uint256 _amount, bytes _msg);

Add the deposit function after the constructor.

function deposit(
// Parameters from users
bytes calldata _msg,
// Parameters from Darknodes
uint256 _amount,
bytes32 _nHash,
bytes calldata _sig
) external {
}

There is another parameter we need to forward to the Shifter: the pHash (also known as the payload hash). It is the hash of any extra data we are using (in our case this is the attached msg). Inside the function body, add:

bytes32 pHash = keccak256(abi.encode(_msg));

We can now get the address of the BTCShifter and call shiftIn. This will return the amount of the zBTC token we have received from the shift, after subtracting a small fee that is paid to RenVM:

uint256 shiftedInAmount = registry.getShifterBySymbol("zBTC").shiftIn(pHash, _amount, _nHash, _sig);

Finally, we log the deposit:

emit Deposit(shiftedInAmount, _msg);

The deposit function should now look like this:

function deposit(
// Parameters from users
bytes calldata _msg,
// Parameters from Darknodes
uint256 _amount,
bytes32 _nHash,
bytes calldata _sig
) external {
bytes32 pHash = keccak256(abi.encode(_msg));
uint256 shiftedInAmount = registry.getShifterBySymbol("zBTC").shiftIn(pHash, _amount, _nHash, _sig);
emit Deposit(shiftedInAmount, _msg);
}

Withdraw function

The withdaw function is similar to the deposit function - we call shiftOut on the Shifter contract and the log the withdrawal.

The user will provide the msg of the withdrawal (as before), a to Bitcoin address to receive the funds to, and the amount of BTC they want to withdraw.

function withdraw(bytes calldata _msg, bytes calldata _to, uint256 _amount) external {
uint256 shiftedOutAmount = registry.getShifterBySymbol("zBTC").shiftOut(_to, _amount);
emit Withdrawal(_to, shiftedOutAmount, _msg);
}

Balance

In balance, instead of getting the address of the BTCShifter, we need the address of the zBTC ERC20 contract. We can use getTokenBySymbol instead of getShifterBySymbol. After looking up the address, we ask it for the balance of our Basic contract:

function balance() public view returns (uint256) {
return registry.getTokenBySymbol("zBTC").balanceOf(address(this));
}

Final code

Basic.sol
pragma solidity >=0.5.0;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
}
interface IShifter {
function shiftIn(bytes32 _pHash, uint256 _amount, bytes32 _nHash, bytes calldata _sig) external returns (uint256);
function shiftOut(bytes calldata _to, uint256 _amount) external returns (uint256);
}
interface IShifterRegistry {
function getShifterBySymbol(string calldata _tokenSymbol) external view returns (IShifter);
function getTokenBySymbol(string calldata _tokenSymbol) external view returns (IERC20);
}
contract Basic {
IShifterRegistry public registry;
event Deposit(uint256 _amount, bytes _msg);
event Withdrawal(bytes _to, uint256 _amount, bytes _msg);
constructor(IShifterRegistry _registry) public {
registry = _registry;
}
function deposit(
// Parameters from users
bytes calldata _msg,
// Parameters from Darknodes
uint256 _amount,
bytes32 _nHash,
bytes calldata _sig
) external {
bytes32 pHash = keccak256(abi.encode(_msg));
uint256 shiftedInAmount = registry.getShifterBySymbol("zBTC").shiftIn(pHash, _amount, _nHash, _sig);
emit Deposit(shiftedInAmount, _msg);
}
function withdraw(bytes calldata _msg, bytes calldata _to, uint256 _amount) external {
uint256 shiftedOutAmount = registry.getShifterBySymbol("zBTC").shiftOut(_to, _amount);
emit Withdrawal(_to, shiftedOutAmount, _msg);
}
function balance() public view returns (uint256) {
return registry.getTokenBySymbol("zBTC").balanceOf(address(this));
}
}

Deploying to Kovan

We have already deployed a version of the Basic contract to Ethereum Kovan. It is available at 0xb2731C04610C10f2eB6A26ad14E607d44309FC10.

You'll need MetaMask installed and the Kovan network selected. Additionally, you'll need Kovan ETH (KETH). Request some here: https://github.com/kovan-testnet/faucet

Copy the final Basic.sol into Remix and then click "Compile Basic.sol" in the "Solidity Compiler" tab.

Switch to the "Deploy & Run Transactions" tab and select "Injected Web3" as the environment. Make sure "Basic" is selected in the contract drop-down. Paste the ShifterRegistry's address, 0xbA563a8510d86dE95F5a50007E180d6d4966ad12, next to "Deploy" and press the "Deploy" button. After approving the transaction in MetaMask, it will show you the newly deployed Ethereum contract. Hit the "clipboard" button and save the address for the next section.

Deploying our Ethereum contract to Kovan

Alternatively, you can use the Basic contract that we have already deployed here: 0xb2731C04610C10f2eB6A26ad14E607d44309FC10.

In the next chapter, we will begin building a simple user interface for interacting with our newly deployed Ethereum contract.