From 6ef71f041998dc87f2bb28dbf996ef7172ad0d9d Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Tue, 12 Oct 2021 16:59:34 +0200 Subject: [PATCH] Introduces StorageContract A StorageContract can only be instantiated when a request for storage and a bid have been signed. --- contracts/StorageContract.sol | 62 +++++++++++++++++++++++++ test/StorageContract.test.js | 86 +++++++++++++++++++++++++++++++++++ test/marketplace.js | 21 +++++++++ 3 files changed, 169 insertions(+) create mode 100644 contracts/StorageContract.sol create mode 100644 test/StorageContract.test.js create mode 100644 test/marketplace.js diff --git a/contracts/StorageContract.sol b/contracts/StorageContract.sol new file mode 100644 index 0000000..8a75f7e --- /dev/null +++ b/contracts/StorageContract.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +contract StorageContract { + uint public immutable duration; // contract duration in seconds + uint public immutable size; // storage size in bytes + uint public immutable price; // price in coins + address public immutable host; // host that provides storage + + constructor(uint _duration, + uint _size, + uint _price, + address _host, + bytes memory requestSignature, + bytes memory bidSignature) + { + bytes32 requestHash = hashRequest(_duration, _size); + bytes32 bidHash = hashBid(requestHash, _price); + checkSignature(requestSignature, requestHash, msg.sender); + checkSignature(bidSignature, bidHash, _host); + duration = _duration; + size = _size; + price = _price; + host = _host; + } + + // creates hash for a storage request that can be used to check its signature + function hashRequest(uint _duration, uint _size) + internal pure + returns (bytes32) + { + return keccak256(abi.encodePacked( + "[dagger.request.v1]", + _duration, + _size + )); + } + + // creates hash for a storage bid that can be used to check its signature + function hashBid(bytes32 requestHash, uint _price) + internal pure + returns (bytes32) + { + return keccak256(abi.encodePacked( + "[dagger.bid.v1]", + requestHash, + _price + )); + } + + // checks a signature for a storage request or bid, given its hash + function checkSignature(bytes memory signature, bytes32 hash, address signer) + internal pure + { + bytes32 messageHash = ECDSA.toEthSignedMessageHash(hash); + address recovered = ECDSA.recover(messageHash, signature); + require(recovered == signer, "Invalid signature"); + } + +} diff --git a/test/StorageContract.test.js b/test/StorageContract.test.js new file mode 100644 index 0000000..8562f5b --- /dev/null +++ b/test/StorageContract.test.js @@ -0,0 +1,86 @@ +const { expect } = require("chai") +const { ethers } = require("hardhat") +const { hashRequest, hashBid, sign } = require("./marketplace") + +describe("Storage Contract", function () { + + const duration = 31 * 24 * 60 * 60 + const size = 1 * 1024 * 1024 * 1024 + const price = 42 + + var StorageContract + var client, host + var requestHash, bidHash + var contract + + beforeEach(async function () { + [client, host] = await ethers.getSigners() + StorageContract = await ethers.getContractFactory("StorageContract") + requestHash = hashRequest(duration, size) + bidHash = hashBid(requestHash, price) + }) + + describe("when properly instantiated", function () { + + beforeEach(async function () { + contract = await StorageContract.deploy( + duration, + size, + price, + await host.getAddress(), + await sign(client, requestHash), + await sign(host, bidHash) + ) + }) + + it("has a duration", async function () { + expect(await contract.duration()).to.equal(duration) + }) + + it("contains the size of the data that is to be stored", async function () { + expect(await contract.size()).to.equal(size) + }) + + it("has a price", async function () { + expect(await contract.price()).to.equal(price) + }) + + it("knows the host that provides the storage", async function () { + expect(await contract.host()).to.equal(await host.getAddress()) + }) + + }) + + it("cannot be created when client signature is invalid", async function () { + let invalidSignature = await sign(client, hashRequest(duration + 1, size)) + await expect(StorageContract.deploy( + duration, + size, + price, + await host.getAddress(), + invalidSignature, + await sign(host, bidHash) + )).to.be.revertedWith("Invalid signature") + }) + + it("cannot be created when host signature is invalid", async function () { + let invalidSignature = await sign(host, hashBid(requestHash, price - 1)) + await expect(StorageContract.deploy( + duration, + size, + price, + await host.getAddress(), + await sign(client, requestHash), + invalidSignature + )).to.be.revertedWith("Invalid signature") + }) +}) + +// TODO: payment on constructor +// TODO: contract start and timeout +// TODO: missed proofs +// TODO: successfull proofs +// TODO: payout +// TODO: stake +// TODO: request timeout +// TODO: bid timeout diff --git a/test/marketplace.js b/test/marketplace.js new file mode 100644 index 0000000..477e882 --- /dev/null +++ b/test/marketplace.js @@ -0,0 +1,21 @@ +const { ethers } = require("hardhat") + +function hashRequest(duration, size) { + return ethers.utils.solidityKeccak256( + ["string", "uint", "uint"], + ["[dagger.request.v1]", duration, size] + ) +} + +function hashBid(requestHash, price) { + return ethers.utils.solidityKeccak256( + ["string", "bytes32", "uint"], + ["[dagger.bid.v1]", requestHash, price] + ) +} + +async function sign(signer, hash) { + return await signer.signMessage(ethers.utils.arrayify(hash)) +} + +module.exports = { hashRequest, hashBid, sign }