diff --git a/contracts/IRelay.sol b/contracts/IRelay.sol index 6628cf1..2126df8 100644 --- a/contracts/IRelay.sol +++ b/contracts/IRelay.sol @@ -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, @@ -75,5 +74,5 @@ interface IRelay { bytes calldata proof, uint256 confirmations, bool insecure - ) external view returns (bool); + ) external payable; } diff --git a/contracts/Relay.sol b/contracts/Relay.sol index 1b77a20..c3a41f5 100644 --- a/contracts/Relay.sol +++ b/contracts/Relay.sol @@ -2,6 +2,7 @@ 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'; @@ -9,7 +10,7 @@ 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; @@ -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) @@ -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 @@ -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(); @@ -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 @@ -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; @@ -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); } /** @@ -198,11 +211,12 @@ 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 @@ -210,6 +224,7 @@ contract Relay is IRelay { _chain[height] = digest; _headers[digest].height = height; _headers[digest].chainId = uint64(chainId); + _headers[digest].author = author; emit StoreHeader(digest, height); } @@ -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); @@ -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; } } diff --git a/contracts/TestRelay.sol b/contracts/TestRelay.sol index e24692a..ff5eda4 100644 --- a/contracts/TestRelay.sol +++ b/contracts/TestRelay.sol @@ -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(); @@ -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; diff --git a/test/gas.test.ts b/test/gas.test.ts index 9e2cd58..a295b72 100644 --- a/test/gas.test.ts +++ b/test/gas.test.ts @@ -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(); diff --git a/test/proof.test.ts b/test/proof.test.ts index 96916cd..08bd2f1 100644 --- a/test/proof.test.ts +++ b/test/proof.test.ts @@ -15,6 +15,8 @@ async function getBestBlockHeight(relay: Relay): Promise { return height; } +const verifyCostWei = 106000; + function deploy( signer: Signer, header: Arrayish, @@ -107,7 +109,8 @@ describe('Proofs', () => { genesisHeader, tx.intermediateNodes, 0, - true + true, + {value: verifyCostWei} ); }); @@ -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 = { @@ -162,7 +167,8 @@ describe('Proofs', () => { testnet1.header, '0x' + proof, 0, - true + true, + {value: verifyCostWei} ); }); @@ -201,7 +207,8 @@ describe('Proofs', () => { testnet2.header, '0x' + proof, 0, - true + true, + {value: verifyCostWei} ); }); @@ -239,7 +246,8 @@ describe('Proofs', () => { testnet3.header, '0x' + proof, 0, - true + true, + {value: verifyCostWei} ); }); @@ -279,7 +287,8 @@ describe('Proofs', () => { '0x' + mainnet1.header, '0x' + proof, 0, - true + true, + {value: verifyCostWei} ); }); });