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