Select a storage offer
This commit is contained in:
parent
cc57155792
commit
85b212c703
|
@ -8,6 +8,7 @@ contract Marketplace is Collateral {
|
||||||
uint256 public immutable collateral;
|
uint256 public immutable collateral;
|
||||||
MarketplaceFunds private funds;
|
MarketplaceFunds private funds;
|
||||||
mapping(bytes32 => Request) private requests;
|
mapping(bytes32 => Request) private requests;
|
||||||
|
mapping(bytes32 => RequestState) private requestState;
|
||||||
mapping(bytes32 => Offer) private offers;
|
mapping(bytes32 => Offer) private offers;
|
||||||
|
|
||||||
constructor(IERC20 _token, uint256 _collateral)
|
constructor(IERC20 _token, uint256 _collateral)
|
||||||
|
@ -45,6 +46,25 @@ contract Marketplace is Collateral {
|
||||||
emit StorageOffered(id, offer);
|
emit StorageOffered(id, offer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectOffer(bytes32 id) public marketplaceInvariant {
|
||||||
|
Offer storage offer = offers[id];
|
||||||
|
require(offer.host != address(0), "Unknown offer");
|
||||||
|
// solhint-disable-next-line not-rely-on-time
|
||||||
|
require(offer.expiry > block.timestamp, "Offer expired");
|
||||||
|
Request storage request = requests[offer.requestId];
|
||||||
|
require(request.client == msg.sender, "Only client can select offer");
|
||||||
|
RequestState storage state = requestState[offer.requestId];
|
||||||
|
require(!state.offerSelected, "Offer already selected");
|
||||||
|
state.offerSelected = true;
|
||||||
|
_createLock(id, offer.expiry);
|
||||||
|
_lock(offer.host, id);
|
||||||
|
_unlock(offer.requestId);
|
||||||
|
uint256 difference = request.maxPrice - offer.price;
|
||||||
|
funds.sent += difference;
|
||||||
|
funds.balance -= difference;
|
||||||
|
token.transfer(request.client, difference);
|
||||||
|
}
|
||||||
|
|
||||||
struct Request {
|
struct Request {
|
||||||
address client;
|
address client;
|
||||||
uint256 duration;
|
uint256 duration;
|
||||||
|
@ -57,6 +77,10 @@ contract Marketplace is Collateral {
|
||||||
bytes32 nonce;
|
bytes32 nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RequestState {
|
||||||
|
bool offerSelected;
|
||||||
|
}
|
||||||
|
|
||||||
struct Offer {
|
struct Offer {
|
||||||
address host;
|
address host;
|
||||||
bytes32 requestId;
|
bytes32 requestId;
|
||||||
|
|
|
@ -9,16 +9,18 @@ describe("Marketplace", function () {
|
||||||
|
|
||||||
let marketplace
|
let marketplace
|
||||||
let token
|
let token
|
||||||
let client, host
|
let client, host, host1, host2, host3
|
||||||
let request, offer
|
let request, offer
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
;[client, host] = await ethers.getSigners()
|
;[client, host1, host2, host3] = await ethers.getSigners()
|
||||||
|
host = host1
|
||||||
|
|
||||||
const TestToken = await ethers.getContractFactory("TestToken")
|
const TestToken = await ethers.getContractFactory("TestToken")
|
||||||
token = await TestToken.deploy()
|
token = await TestToken.deploy()
|
||||||
await token.mint(client.address, 1000)
|
for (account of [client, host1, host2, host3]) {
|
||||||
await token.mint(host.address, 1000)
|
await token.mint(account.address, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
const Marketplace = await ethers.getContractFactory("Marketplace")
|
const Marketplace = await ethers.getContractFactory("Marketplace")
|
||||||
marketplace = await Marketplace.deploy(token.address, collateral)
|
marketplace = await Marketplace.deploy(token.address, collateral)
|
||||||
|
@ -133,6 +135,76 @@ describe("Marketplace", function () {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("selecting an offer", async function () {
|
||||||
|
beforeEach(async function () {
|
||||||
|
switchAccount(client)
|
||||||
|
await token.approve(marketplace.address, request.maxPrice)
|
||||||
|
await marketplace.requestStorage(request)
|
||||||
|
for (host of [host1, host2, host3]) {
|
||||||
|
switchAccount(host)
|
||||||
|
let hostOffer = { ...offer, host: host.address }
|
||||||
|
await token.approve(marketplace.address, collateral)
|
||||||
|
await marketplace.deposit(collateral)
|
||||||
|
await marketplace.offerStorage(hostOffer)
|
||||||
|
}
|
||||||
|
switchAccount(client)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns price difference to client", async function () {
|
||||||
|
let difference = request.maxPrice - offer.price
|
||||||
|
let before = await token.balanceOf(client.address)
|
||||||
|
await marketplace.selectOffer(offerId(offer))
|
||||||
|
let after = await token.balanceOf(client.address)
|
||||||
|
expect(after - before).to.equal(difference)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("unlocks collateral of hosts that weren't chosen", async function () {
|
||||||
|
await marketplace.selectOffer(offerId(offer))
|
||||||
|
switchAccount(host2)
|
||||||
|
await expect(marketplace.withdraw()).not.to.be.reverted
|
||||||
|
switchAccount(host3)
|
||||||
|
await expect(marketplace.withdraw()).not.to.be.reverted
|
||||||
|
})
|
||||||
|
|
||||||
|
it("locks collateral of host that was chosen", async function () {
|
||||||
|
await marketplace.selectOffer(offerId(offer))
|
||||||
|
switchAccount(host1)
|
||||||
|
await expect(marketplace.withdraw()).to.be.revertedWith("Account locked")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("rejects selection of unknown offer", async function () {
|
||||||
|
let unknown = exampleOffer()
|
||||||
|
await expect(
|
||||||
|
marketplace.selectOffer(offerId(unknown))
|
||||||
|
).to.be.revertedWith("Unknown offer")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("rejects selection of expired offer", async function () {
|
||||||
|
let expired = { ...offer, expiry: now() - hours(1) }
|
||||||
|
switchAccount(host1)
|
||||||
|
await marketplace.offerStorage(expired)
|
||||||
|
switchAccount(client)
|
||||||
|
await expect(
|
||||||
|
marketplace.selectOffer(offerId(expired))
|
||||||
|
).to.be.revertedWith("Offer expired")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("rejects reselection of offer", async function () {
|
||||||
|
let secondOffer = { ...offer, host: host2.address }
|
||||||
|
await marketplace.selectOffer(offerId(offer))
|
||||||
|
await expect(
|
||||||
|
marketplace.selectOffer(offerId(secondOffer))
|
||||||
|
).to.be.revertedWith("Offer already selected")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("rejects selection by anyone other than the client", async function () {
|
||||||
|
switchAccount(host1)
|
||||||
|
await expect(marketplace.selectOffer(offerId(offer))).to.be.revertedWith(
|
||||||
|
"Only client can select offer"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function requestId(request) {
|
function requestId(request) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ const exampleRequest = () => ({
|
||||||
contentHash: 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
|
||||||
maxPrice: 42,
|
maxPrice: 84,
|
||||||
expiry: now() + hours(1),
|
expiry: now() + hours(1),
|
||||||
nonce: hexlify(randomBytes(32)),
|
nonce: hexlify(randomBytes(32)),
|
||||||
})
|
})
|
||||||
|
@ -20,6 +20,7 @@ const exampleBid = () => ({
|
||||||
})
|
})
|
||||||
|
|
||||||
const exampleOffer = () => ({
|
const exampleOffer = () => ({
|
||||||
|
requestId: hexlify(randomBytes(32)),
|
||||||
host: hexlify(randomBytes(20)),
|
host: hexlify(randomBytes(20)),
|
||||||
price: 42,
|
price: 42,
|
||||||
expiry: now() + hours(1),
|
expiry: now() + hours(1),
|
||||||
|
|
Loading…
Reference in New Issue