Request storage using Marketplace contract

This commit is contained in:
Mark Spanbroek 2022-02-16 10:50:00 +01:00 committed by markspanbroek
parent b15f4e749b
commit e1ef2a2216
3 changed files with 144 additions and 3 deletions

56
contracts/Marketplace.sol Normal file
View File

@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Marketplace {
IERC20 public immutable token;
Totals private totals;
mapping(bytes32 => Request) private requests;
constructor(IERC20 _token) invariant {
token = _token;
}
function transferFrom(address sender, uint256 amount) private {
address receiver = address(this);
require(token.transferFrom(sender, receiver, amount), "Transfer failed");
}
function requestStorage(Request calldata request) public invariant {
bytes32 id = keccak256(abi.encode(request));
require(request.size > 0, "Invalid size");
require(requests[id].size == 0, "Request already exists");
requests[id] = request;
transferFrom(msg.sender, request.maxPrice);
totals.received += request.maxPrice;
totals.balance += request.maxPrice;
emit StorageRequested(id, request);
}
struct Request {
uint256 duration;
uint256 size;
bytes32 contentHash;
uint256 proofPeriod;
uint256 proofTimeout;
uint256 maxPrice;
bytes32 nonce;
}
event StorageRequested(bytes32 id, Request request);
modifier invariant() {
Totals memory oldTotals = totals;
_;
assert(totals.received >= oldTotals.received);
assert(totals.sent >= oldTotals.sent);
assert(totals.received == totals.balance + totals.sent);
}
struct Totals {
uint256 balance;
uint256 received;
uint256 sent;
}
}

83
test/Marketplace.test.js Normal file
View File

@ -0,0 +1,83 @@
const { ethers } = require("hardhat")
const { expect } = require("chai")
const { exampleRequest } = require("./examples")
const { keccak256, defaultAbiCoder } = ethers.utils
describe("Marketplace", function () {
const request = exampleRequest()
let marketplace
let token
let accounts
beforeEach(async function () {
const TestToken = await ethers.getContractFactory("TestToken")
token = await TestToken.deploy()
const Marketplace = await ethers.getContractFactory("Marketplace")
marketplace = await Marketplace.deploy(token.address)
accounts = await ethers.getSigners()
await token.mint(accounts[0].address, 1000)
})
describe("requesting storage", function () {
it("emits event when storage is requested", async function () {
await token.approve(marketplace.address, request.maxPrice)
await expect(marketplace.requestStorage(request))
.to.emit(marketplace, "StorageRequested")
.withArgs(requestId(request), requestToArray(request))
})
it("rejects request with insufficient payment", async function () {
let insufficient = request.maxPrice - 1
await token.approve(marketplace.address, insufficient)
await expect(marketplace.requestStorage(request)).to.be.revertedWith(
"ERC20: transfer amount exceeds allowance"
)
})
it("rejects requests of size 0", async function () {
let invalid = { ...request, size: 0 }
await token.approve(marketplace.address, invalid.maxPrice)
await expect(marketplace.requestStorage(invalid)).to.be.revertedWith(
"Invalid size"
)
})
it("rejects resubmission of request", async function () {
await token.approve(marketplace.address, request.maxPrice * 2)
await marketplace.requestStorage(request)
await expect(marketplace.requestStorage(request)).to.be.revertedWith(
"Request already exists"
)
})
})
})
function requestId(request) {
return keccak256(
defaultAbiCoder.encode(
[
"uint256",
"uint256",
"bytes32",
"uint256",
"uint256",
"uint256",
"bytes32",
],
requestToArray(request)
)
)
}
function requestToArray(request) {
return [
request.duration,
request.size,
request.contentHash,
request.proofPeriod,
request.proofTimeout,
request.maxPrice,
request.nonce,
]
}

View File

@ -1,13 +1,15 @@
const { ethers } = require("hardhat") const { ethers } = require("hardhat")
const { now, hours } = require("./time") const { now, hours } = require("./time")
const { sha256, hexlify, randomBytes } = ethers.utils
const exampleRequest = () => ({ const exampleRequest = () => ({
duration: 150, // 150 blocks ≈ half an hour duration: 150, // 150 blocks ≈ half an hour
size: 1 * 1024 * 1024 * 1024, // 1 Gigabyte size: 1 * 1024 * 1024 * 1024, // 1 Gigabyte
contentHash: ethers.utils.sha256("0xdeadbeef"), contentHash: sha256("0xdeadbeef"),
proofPeriod: 8, // 8 blocks ≈ 2 minutes proofPeriod: 8, // 8 blocks ≈ 2 minutes
proofTimeout: 4, // 4 blocks ≈ 1 minute proofTimeout: 4, // 4 blocks ≈ 1 minute
nonce: ethers.utils.randomBytes(32), maxPrice: 42,
nonce: hexlify(randomBytes(32)),
}) })
const exampleBid = () => ({ const exampleBid = () => ({
@ -16,7 +18,7 @@ const exampleBid = () => ({
}) })
const exampleLock = () => ({ const exampleLock = () => ({
id: ethers.utils.randomBytes(32), id: hexlify(randomBytes(32)),
expiry: now() + hours(1), expiry: now() + hours(1),
}) })