Request storage using Marketplace contract
This commit is contained in:
parent
b15f4e749b
commit
e1ef2a2216
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
]
|
||||||
|
}
|
|
@ -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),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue