Skip to content
This repository has been archived by the owner on Jun 21, 2024. It is now read-only.

add basic fee model to incentivize relayers #3

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions contracts/IRelay.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ interface IRelay {
* @param proof Merkle proof
* @param confirmations Required confirmations (insecure)
* @param insecure Check custom inclusion confirmations
* @return True if txid is included, false otherwise
*/
function verifyTx(
uint32 height,
Expand All @@ -75,5 +74,5 @@ interface IRelay {
bytes calldata proof,
uint256 confirmations,
bool insecure
) external view returns (bool);
) external payable;
}
40 changes: 29 additions & 11 deletions contracts/Relay.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

pragma solidity ^0.6.0;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
import {BytesLib} from '@interlay/bitcoin-spv-sol/contracts/BytesLib.sol';
import {BTCUtils} from '@interlay/bitcoin-spv-sol/contracts/BTCUtils.sol';
import {ValidateSPV} from '@interlay/bitcoin-spv-sol/contracts/ValidateSPV.sol';
import {IRelay} from './IRelay.sol';

/// @title BTC Relay
contract Relay is IRelay {
contract Relay is IRelay, Ownable {
using SafeMath for uint256;
using BytesLib for bytes;
using BTCUtils for bytes;
Expand All @@ -19,6 +20,8 @@ contract Relay is IRelay {
uint32 height;
// identifier of chain fork
uint64 chainId;
// relayer account
address payable author;
}

// mapping of block hashes to block headers (ALL ever submitted, i.e., incl. forks)
Expand Down Expand Up @@ -56,6 +59,8 @@ contract Relay is IRelay {
uint256 public constant MAIN_CHAIN_ID = 0;
uint256 public constant CONFIRMATIONS = 6;

uint256 public verifyCostWei = 106000;

// EXCEPTION MESSAGES
// OPTIMIZATION: limit string length to 32 bytes
string
Expand All @@ -76,13 +81,14 @@ contract Relay is IRelay {
string internal constant ERR_CONFIRMS = 'Insufficient confirmations';
string internal constant ERR_VERIFY_TX = 'Incorrect merkle proof';
string internal constant ERR_INVALID_TXID = 'Invalid tx identifier';
string internal constant ERR_INSUFFICIENT_PAYMENT = 'Insufficient payment';

/**
* @notice Initializes the relay with the provided block.
* @param header Genesis block header
* @param height Genesis block height
*/
constructor(bytes memory header, uint32 height) public {
constructor(bytes memory header, uint32 height) public Ownable() {
require(header.length == 80, ERR_INVALID_HEADER_SIZE);
require(height > 0, ERR_INVALID_GENESIS_HEIGHT);
bytes32 digest = header.hash256();
Expand All @@ -101,13 +107,20 @@ contract Relay is IRelay {
_epochEndTarget = target;
_epochEndTime = timestamp;

_storeBlockHeader(digest, height, chainId);
_storeBlockHeader(msg.sender, digest, height, chainId);
}

function setVerifyCostWei(uint256 price) external onlyOwner {
verifyCostWei = price;
}

/**
* @dev Core logic for block header validation
*/
function _submitBlockHeader(bytes memory header) internal virtual {
function _submitBlockHeader(address payable author, bytes memory header)
internal
virtual
{
require(header.length == 80, ERR_INVALID_HEADER_SIZE);

// Fail if block already exists
Expand Down Expand Up @@ -161,9 +174,9 @@ contract Relay is IRelay {
chainId = _incrementChainCounter();
_initializeFork(hashCurrBlock, hashPrevBlock, chainId, height);

_storeBlockHeader(hashCurrBlock, height, chainId);
_storeBlockHeader(author, hashCurrBlock, height, chainId);
} else {
_storeBlockHeader(hashCurrBlock, height, chainId);
_storeBlockHeader(author, hashCurrBlock, height, chainId);

if (chainId == MAIN_CHAIN_ID) {
_bestBlock = hashCurrBlock;
Expand All @@ -187,7 +200,7 @@ contract Relay is IRelay {
* @dev See {IRelay-submitBlockHeader}.
*/
function submitBlockHeader(bytes calldata header) external override {
_submitBlockHeader(header);
_submitBlockHeader(msg.sender, header);
}

/**
Expand All @@ -198,18 +211,20 @@ contract Relay is IRelay {

for (uint256 i = 0; i < headers.length / 80; i = i.add(1)) {
bytes memory header = headers.slice(i.mul(80), 80);
_submitBlockHeader(header);
_submitBlockHeader(msg.sender, header);
}
}

function _storeBlockHeader(
address payable author,
bytes32 digest,
uint32 height,
uint256 chainId
) internal {
_chain[height] = digest;
_headers[digest].height = height;
_headers[digest].chainId = uint64(chainId);
_headers[digest].author = author;
emit StoreHeader(digest, height);
}

Expand Down Expand Up @@ -371,7 +386,7 @@ contract Relay is IRelay {
bytes calldata proof,
uint256 confirmations,
bool insecure
) external override view returns (bool) {
) external override payable {
// txid must be little endian
require(txid != 0, ERR_INVALID_TXID);

Expand All @@ -382,9 +397,12 @@ contract Relay is IRelay {
}

require(_chain[height] == header.hash256(), ERR_BLOCK_NOT_FOUND);

// recoup fees
require(msg.value >= verifyCostWei, ERR_INSUFFICIENT_PAYMENT);
_headers[_chain[height]].author.transfer(msg.value);

bytes32 root = header.extractMerkleRootLE().toBytes32();
require(ValidateSPV.prove(txid, root, proof, index), ERR_VERIFY_TX);

return true;
}
}
9 changes: 6 additions & 3 deletions contracts/TestRelay.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ contract TestRelay is Relay {
/**
* @dev Override to remove the difficulty check
*/
function _submitBlockHeader(bytes memory header) internal override {
function _submitBlockHeader(address payable author, bytes memory header)
internal
override
{
require(header.length == 80, ERR_INVALID_HEADER_SIZE);

bytes32 hashPrevBlock = header.extractPrevBlockLE().toBytes32();
Expand Down Expand Up @@ -62,9 +65,9 @@ contract TestRelay is Relay {
chainId = _incrementChainCounter();
_initializeFork(hashCurrBlock, hashPrevBlock, chainId, height);

_storeBlockHeader(hashCurrBlock, height, chainId);
_storeBlockHeader(author, hashCurrBlock, height, chainId);
} else {
_storeBlockHeader(hashCurrBlock, height, chainId);
_storeBlockHeader(author, hashCurrBlock, height, chainId);

if (chainId == MAIN_CHAIN_ID) {
_bestBlock = hashCurrBlock;
Expand Down
2 changes: 1 addition & 1 deletion test/gas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('Gas', () => {
await relay.deployTransaction.wait(1)
).gasUsed?.toNumber();
// console.log(`Deploy: ${deployCost}`);
expect(deployCost).to.be.lt(2_000_000);
expect(deployCost).to.be.lt(2_300_000);

const result = await relay.submitBlockHeader(header1);
const updateCost = (await result.wait(1)).gasUsed?.toNumber();
Expand Down
21 changes: 15 additions & 6 deletions test/proof.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ async function getBestBlockHeight(relay: Relay): Promise<number> {
return height;
}

const verifyCostWei = 106000;

function deploy(
signer: Signer,
header: Arrayish,
Expand Down Expand Up @@ -107,7 +109,8 @@ describe('Proofs', () => {
genesisHeader,
tx.intermediateNodes,
0,
true
true,
{value: verifyCostWei}
);
});

Expand All @@ -124,7 +127,9 @@ describe('Proofs', () => {
'hex'
).reverse();

await relay.verifyTx(height, 0, txId, header, [], 0, true);
await relay.verifyTx(height, 0, txId, header, [], 0, true, {
value: verifyCostWei
});
});

const testnet1 = {
Expand Down Expand Up @@ -162,7 +167,8 @@ describe('Proofs', () => {
testnet1.header,
'0x' + proof,
0,
true
true,
{value: verifyCostWei}
);
});

Expand Down Expand Up @@ -201,7 +207,8 @@ describe('Proofs', () => {
testnet2.header,
'0x' + proof,
0,
true
true,
{value: verifyCostWei}
);
});

Expand Down Expand Up @@ -239,7 +246,8 @@ describe('Proofs', () => {
testnet3.header,
'0x' + proof,
0,
true
true,
{value: verifyCostWei}
);
});

Expand Down Expand Up @@ -279,7 +287,8 @@ describe('Proofs', () => {
'0x' + mainnet1.header,
'0x' + proof,
0,
true
true,
{value: verifyCostWei}
);
});
});