From f59bfc3c9cee3e6ad4e76d8465fd70e12b777ad7 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 10 Mar 2024 22:29:51 -0500 Subject: [PATCH] stake availability --- ethereum/contracts/DAO.sol | 17 ++++++++ ethereum/contracts/IAcceptAvailability.sol | 10 +++++ ethereum/contracts/ReputationHolder.sol | 15 ------- ethereum/contracts/Work1.sol | 21 +++++----- ethereum/test/DAO.js | 9 +++-- ethereum/test/Work1.js | 47 ++++++++++++++++++++++ 6 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 ethereum/contracts/IAcceptAvailability.sol delete mode 100644 ethereum/contracts/ReputationHolder.sol create mode 100644 ethereum/test/Work1.js diff --git a/ethereum/contracts/DAO.sol b/ethereum/contracts/DAO.sol index d36cc62..f67f5e0 100644 --- a/ethereum/contracts/DAO.sol +++ b/ethereum/contracts/DAO.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.24; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./IAcceptAvailability.sol"; struct Stake { bool inFavor; @@ -69,6 +70,7 @@ contract DAO is ERC20("Reputation", "REP") { // TODO: Make minting ratio an adjustable parameter // TODO: Make stakeForAuthor an adjustable parameter _mint(address(this), msg.value); + // TODO: We need a way to exclude this pending reputation from the total supply when computing fee distribution _stake(pool, author, msg.value / 2, true); _stake(pool, author, msg.value / 2, false); emit ValidationPoolInitiated(poolIndex); @@ -144,4 +146,19 @@ contract DAO is ERC20("Reputation", "REP") { payable(member).transfer(share); } } + + /// Transfer REP to a contract, and call that contract's receiveTransfer method + function stakeAvailability( + address to, + uint256 value, + uint duration + ) external returns (bool transferred) { + transferred = super.transfer(to, value); + if (transferred) + IAcceptAvailability(to).acceptAvailability( + msg.sender, + value, + duration + ); + } } diff --git a/ethereum/contracts/IAcceptAvailability.sol b/ethereum/contracts/IAcceptAvailability.sol new file mode 100644 index 0000000..9643466 --- /dev/null +++ b/ethereum/contracts/IAcceptAvailability.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.24; + +interface IAcceptAvailability { + function acceptAvailability( + address from, + uint256 value, + uint duration + ) external; +} diff --git a/ethereum/contracts/ReputationHolder.sol b/ethereum/contracts/ReputationHolder.sol deleted file mode 100644 index 6bfe6b7..0000000 --- a/ethereum/contracts/ReputationHolder.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.24; - -import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; - -abstract contract ReputationHolder is IERC721Receiver { - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) public virtual returns (bytes4) { - return IERC721Receiver.onERC721Received.selector; - } -} diff --git a/ethereum/contracts/Work1.sol b/ethereum/contracts/Work1.sol index 11e9600..8dfd5d3 100644 --- a/ethereum/contracts/Work1.sol +++ b/ethereum/contracts/Work1.sol @@ -2,11 +2,12 @@ pragma solidity ^0.8.24; import "./DAO.sol"; -import "./ReputationHolder.sol"; +import "./IAcceptAvailability.sol"; struct AvailabilityStake { address worker; uint256 amount; + uint duration; bool assigned; } @@ -26,11 +27,11 @@ struct WorkRequest { uint poolIndex; } -contract Work1 is ReputationHolder { +contract Work1 is IAcceptAvailability { DAO immutable dao; uint public immutable price; - mapping(uint => AvailabilityStake) stakes; - uint stakeCount; + mapping(uint => AvailabilityStake) public stakes; + uint public stakeCount; mapping(uint => WorkRequest) public requests; uint public requestCount; @@ -45,13 +46,15 @@ contract Work1 is ReputationHolder { } /// Accept availability stakes as reputation token transfer - function stakeAvailability(uint256 amount) public { - require(dao.balanceOf(msg.sender) >= amount); + function acceptAvailability( + address sender, + uint256 amount, + uint duration + ) external { AvailabilityStake storage stake = stakes[stakeCount++]; - stake.worker = msg.sender; + stake.worker = sender; stake.amount = amount; - // TODO: Token locking - // TODO: Duration + stake.duration = duration; } /// Select a worker randomly from among the available workers, weighted by amount staked diff --git a/ethereum/test/DAO.js b/ethereum/test/DAO.js index db1ac8d..4fb8b3b 100644 --- a/ethereum/test/DAO.js +++ b/ethereum/test/DAO.js @@ -9,7 +9,7 @@ describe('DAO', () => { // We define a fixture to reuse the same setup in every test. // We use loadFixture to run this setup once, snapshot that state, // and reset Hardhat Network to that snapshot in every test. - async function deployDAO() { + async function deploy() { // Contracts are deployed using the first signer/account by default const [account1, account2] = await ethers.getSigners(); @@ -20,7 +20,7 @@ describe('DAO', () => { } it('Should deploy', async () => { - const { dao } = await loadFixture(deployDAO); + const { dao } = await loadFixture(deploy); expect(dao).to.exist; expect(await dao.totalValue()).to.equal(0); }); @@ -32,7 +32,7 @@ describe('DAO', () => { const fee = 100; beforeEach(async () => { - const setup = await loadFixture(deployDAO); + const setup = await loadFixture(deploy); dao = setup.dao; account1 = setup.account1; const init = () => dao.initiateValidationPool(account1, POOL_DURATION, { value: fee }); @@ -40,10 +40,11 @@ describe('DAO', () => { expect(await dao.validationPoolCount()).to.equal(1); expect(await dao.memberCount()).to.equal(0); expect(await dao.balanceOf(account1)).to.equal(0); + expect(await dao.totalSupply()).to.equal(fee); }); it('should not be able to initiate a validation pool without a fee', async () => { - const setup = await loadFixture(deployDAO); + const setup = await loadFixture(deploy); const init = () => setup.dao.initiateValidationPool(setup.account1, POOL_DURATION); await expect(init()).to.be.revertedWith('Fee is required to initiate validation pool'); }); diff --git a/ethereum/test/Work1.js b/ethereum/test/Work1.js new file mode 100644 index 0000000..78493bf --- /dev/null +++ b/ethereum/test/Work1.js @@ -0,0 +1,47 @@ +const { + loadFixture, +} = require('@nomicfoundation/hardhat-toolbox/network-helpers'); +const { expect } = require('chai'); +const { ethers } = require('hardhat'); + +describe('Work1', () => { + const WORK1_PRICE = 100; + async function deploy() { + // Contracts are deployed using the first signer/account by default + const [account1, account2] = await ethers.getSigners(); + + const DAO = await ethers.getContractFactory('DAO'); + const dao = await DAO.deploy(); + const Work1 = await ethers.getContractFactory('Work1'); + const work1 = await Work1.deploy(dao.target, WORK1_PRICE); + + await dao.initiateValidationPool(account1, 0, { value: 100 }); + await dao.evaluateOutcome(0); + + return { + dao, work1, account1, account2, + }; + } + + it('Should deploy', async () => { + const { dao, work1, account1 } = await loadFixture(deploy); + expect(dao).to.exist; + expect(work1).to.exist; + expect(await dao.memberCount()).to.equal(1); + expect(await dao.balanceOf(account1)).to.equal(100); + expect(await dao.totalSupply()).to.equal(100); + expect(await work1.stakeCount()).to.equal(0); + }); + + it('Should be able to receive availability stake', async () => { + const { dao, work1, account1 } = await loadFixture(deploy); + await dao.stakeAvailability(work1.target, 50, 60); + expect(await dao.balanceOf(account1)).to.equal(50); + expect(await dao.balanceOf(work1.target)).to.equal(50); + expect(await work1.stakeCount()).to.equal(1); + const stake = await work1.stakes(0); + expect(stake.worker).to.equal(account1); + expect(stake.amount).to.equal(50); + expect(stake.duration).to.equal(60); + }); +});