implemented price change proposal workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 26s Details

This commit is contained in:
Ladd Hoffman 2024-04-02 13:36:40 -05:00
parent e3d194a7bd
commit e3b6026d18
23 changed files with 305 additions and 165 deletions

View File

@ -15,7 +15,7 @@ import Stack from 'react-bootstrap/Stack';
import './App.css';
import useList from './utils/List';
import { getContractAddressByChainId, getContractNameByAddress } from './utils/contract-config';
import { getContractAddressByChainId } from './utils/contract-config';
import Web3Context from './contexts/Web3Context';
import DAOArtifact from './assets/DAO.json';
import Work1Artifact from './assets/Work1.json';
@ -25,6 +25,7 @@ import AddPostModal from './components/posts/AddPostModal';
import ViewPostModal from './components/posts/ViewPostModal';
import Post from './utils/Post';
import Proposals from './components/Proposals';
import getAddressName from './utils/get-address-name';
function App() {
const {
@ -296,17 +297,6 @@ function App() {
/* --------------------------- END UI ACTIONS ------------------------------------- */
/* -------------------------------------------------------------------------------- */
const getAddressName = useCallback((address) => {
const contractName = getContractNameByAddress(chainId, address);
if (contractName) return `${contractName} Contract`;
const addressParts = [
address.slice(0, 7),
address.slice(address.length - 5),
];
return addressParts.join('...');
}, [chainId]);
return (
<Web3Context.Provider value={web3ProviderValue}>
@ -382,8 +372,8 @@ function App() {
{posts.filter((x) => !!x).map((post) => (
<tr key={post.id}>
<td>{post.id.toString()}</td>
<td>{getAddressName(post.author)}</td>
<td>{getAddressName(post.sender)}</td>
<td>{getAddressName(chainId, post.author)}</td>
<td>{getAddressName(chainId, post.sender)}</td>
<td>
<Button onClick={() => handleShowViewPost(post)}>
View Post
@ -392,15 +382,15 @@ function App() {
Initiate Validation Pool
{' '}
<Button onClick={() => initiateValidationPool(post.id, 1)}>
1 s
1s
</Button>
{' '}
<Button onClick={() => initiateValidationPool(post.id, 3600)}>
1 Hr.
<Button onClick={() => initiateValidationPool(post.id, 20)}>
20s
</Button>
{' '}
<Button onClick={() => initiateValidationPool(post.id, 86400)}>
1 Day
<Button onClick={() => initiateValidationPool(post.id, 60)}>
60s
</Button>
</td>
</tr>
@ -435,6 +425,7 @@ function App() {
<br />
Count
</th>
<th>Callback Ret Code</th>
<th>Status</th>
<th>Actions</th>
</tr>
@ -444,7 +435,7 @@ function App() {
<tr key={pool.id}>
<td>{pool.id.toString()}</td>
<td>{pool.postIndex.toString()}</td>
<td>{getAddressName(pool.sender)}</td>
<td>{getAddressName(chainId, pool.sender)}</td>
<td>{pool.fee.toString()}</td>
<td>
{pool.params.bindingPercent.toString()}
@ -456,6 +447,7 @@ function App() {
<td>{pool.params.duration.toString()}</td>
<td>{new Date(Number(pool.endTime) * 1000).toLocaleString()}</td>
<td>{pool.stakeCount.toString()}</td>
<td>{pool.onValidateRetCode.toString()}</td>
<td>{pool.status}</td>
<td>
{!pool.resolved && reputation > 0 && pool.timeRemaining > 0 && (
@ -464,9 +456,13 @@ function App() {
Stake 1/2 REP
</Button>
{' '}
<Button onClick={() => stake(pool.id, reputation, true)}>
Stake All
</Button>
{' '}
</>
)}
{!pool.resolved && pool.timeRemaining <= 0 && (
{!pool.resolved && (pool.timeRemaining <= 0 || !reputation) && (
<Button onClick={() => evaluateOutcome(pool.id)}>
Evaluate Outcome
</Button>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,6 +12,7 @@ import { getContractAddressByChainId } from '../utils/contract-config';
import AddPostModal from './posts/AddPostModal';
import ViewPostModal from './posts/ViewPostModal';
import Post from '../utils/Post';
import getAddressName from '../utils/get-address-name';
const getProposalStatus = (proposal) => {
switch (Number(proposal.stage)) {
@ -107,9 +108,8 @@ function Proposals() {
// TODO: Make referenda durations configurable
await proposalsContract.current.methods.propose(
post.hash,
durations[0],
durations[1],
durations[2],
account,
durations,
false,
emptyCallbackData,
).send({
@ -141,7 +141,7 @@ function Proposals() {
const pool = proposal.pools[referendumIndex][i];
if (pool.started) {
referenda.push(
<div key={`${referendumIndex}.{i}`}>
<div key={`${referendumIndex}.${i}`}>
{`${referendumIndex}.${i}. `}
{!pool.completed && (
<span>
@ -189,6 +189,7 @@ function Proposals() {
<thead>
<tr>
<th>ID</th>
<th>Sender</th>
<th>Fee</th>
<th>Stage</th>
<th>Attestation</th>
@ -200,6 +201,7 @@ function Proposals() {
{proposals.filter((x) => !!x).map((proposal) => (
<tr key={proposal.id}>
<td>{proposal.id.toString()}</td>
<td>{getAddressName(chainId, proposal.sender)}</td>
<td>{proposal.fee.toString()}</td>
<td>{getProposalStatus(proposal)}</td>
<td>{proposal.attestationTotal.toString()}</td>

View File

@ -25,8 +25,17 @@ function PriceProposals() {
useEffect(() => {
fetchPriceProposals();
// TODO: Event subscriptions/unsubscriptions
}, [workContract, fetchPriceProposals]);
const onPriceChangeProposed = (event) => {
fetchPriceProposal(event.returnValues.priceProposalIndex);
};
workContract.events.PriceChangeProposed({ fromBlock: 'latest' }).on('data', onPriceChangeProposed);
return () => {
workContract.events.PriceChangeProposed().off(onPriceChangeProposed);
};
}, [workContract, fetchPriceProposals, fetchPriceProposal]);
return (
<>

View File

@ -44,9 +44,6 @@ function ProposePriceChangeModal({
setShow(false);
// Write to API
await post.write();
// Publish to blockchain -- For now, Proposals.propose() does this for us
// await post.publish(DAO, account);
// Use content hash when calling Proposals.propose
// TODO: Make durations configurable
await workContract.methods.proposeNewPrice(proposedPrice, post.hash, [30, 30, 30]).send({
from: account,

View File

@ -39,7 +39,20 @@ function WorkContract({
}, [workContract, provider]);
useEffect(() => {
const web3 = new Web3(provider);
fetchPrice();
const onPriceChangeAccepted = (event) => {
setPriceWei(event.returnValues.price);
setPriceEth(web3.utils.fromWei(event.returnValues.price, 'ether'));
};
workContract.events.PriceChangeAccepted({ fromBlock: 'latest' }).on('data', onPriceChangeAccepted);
return () => {
workContract.events.PriceChangeAccepted().off(onPriceChangeAccepted);
};
}, [workContract, provider, fetchPrice]);
const workContractProviderValue = useMemo(() => ({

View File

@ -1,14 +1,14 @@
{
"localhost": {
"DAO": "0x358A07B26F4c556140872ecdB69c58e8807E7178",
"Work1": "0xC62b0b16B3ef06c417BFC4Fb02E0Da06aF5A95Ef",
"Onboarding": "0x91B8D37F396cfb887996119CD37a0886C78a7B9C",
"Proposals": "0x63472674239ffb70618Fae043610917f2d9B781C"
"DAO": "0x614fE39E47E48Ed39a078791917F6290C1C8D0cd",
"Work1": "0x35d9024a19e970b1454Fa1C0e124dD2bd71E6360",
"Onboarding": "0x861fD16fA5C26c53bf6C6E6210dD4d2364A26213",
"Proposals": "0x918040581A6817fa2F3F6e73f7F10C3A3a7Bbc29"
},
"sepolia": {
"DAO": "0x58c8ea0ba031431423cD84787d7d57F0Bf7c6E63",
"Work1": "0x67F6944504bF1b99fC6878AE7234A4dB6AB6dd1E",
"Onboarding": "0xA44D0CAba4CB76e32966ea41dcECBc8A98347E68",
"Proposals": "0x5A13A264214CDBfc949522dcCc48E5A0B11B3EdD"
"DAO": "0x58c41E768aCA55B39b5dC0618c0D0bE3f5519943",
"Work1": "0x6cEca2BB849c2a00786A05ed4fC64D08905724Cc",
"Onboarding": "0x4b3906a6356F387bF5dd26FD34B072d20Cd40a7b",
"Proposals": "0x3E1A6EE8D24Ba7D1392104B8652Bb0D2BDF127EE"
}
}

View File

@ -0,0 +1,14 @@
import { getContractNameByAddress } from './contract-config';
const getAddressName = (chainId, address) => {
const contractName = getContractNameByAddress(chainId, address);
if (contractName) return `${contractName} Contract`;
const addressParts = [
address.slice(0, 7),
address.slice(address.length - 5),
];
return addressParts.join('...');
};
export default getAddressName;

View File

@ -1,14 +1,14 @@
{
"localhost": {
"DAO": "0x358A07B26F4c556140872ecdB69c58e8807E7178",
"Work1": "0xC62b0b16B3ef06c417BFC4Fb02E0Da06aF5A95Ef",
"Onboarding": "0x91B8D37F396cfb887996119CD37a0886C78a7B9C",
"Proposals": "0x63472674239ffb70618Fae043610917f2d9B781C"
"DAO": "0x614fE39E47E48Ed39a078791917F6290C1C8D0cd",
"Work1": "0x35d9024a19e970b1454Fa1C0e124dD2bd71E6360",
"Onboarding": "0x861fD16fA5C26c53bf6C6E6210dD4d2364A26213",
"Proposals": "0x918040581A6817fa2F3F6e73f7F10C3A3a7Bbc29"
},
"sepolia": {
"DAO": "0x58c8ea0ba031431423cD84787d7d57F0Bf7c6E63",
"Work1": "0x67F6944504bF1b99fC6878AE7234A4dB6AB6dd1E",
"Onboarding": "0xA44D0CAba4CB76e32966ea41dcECBc8A98347E68",
"Proposals": "0x5A13A264214CDBfc949522dcCc48E5A0B11B3EdD"
"DAO": "0x58c41E768aCA55B39b5dC0618c0D0bE3f5519943",
"Work1": "0x6cEca2BB849c2a00786A05ed4fC64D08905724Cc",
"Onboarding": "0x4b3906a6356F387bF5dd26FD34B072d20Cd40a7b",
"Proposals": "0x3E1A6EE8D24Ba7D1392104B8652Bb0D2BDF127EE"
}
}

View File

@ -169,10 +169,6 @@ contract DAO is ERC20("Reputation", "REP") {
function evaluateOutcome(uint poolIndex) public returns (bool votePasses) {
ValidationPool storage pool = validationPools[poolIndex];
Post storage post = posts[pool.postIndex];
require(
block.timestamp > pool.endTime,
"Pool end time has not yet arrived"
);
require(pool.resolved == false, "Pool is already resolved");
uint256 stakedFor;
uint256 stakedAgainst;
@ -185,6 +181,12 @@ contract DAO is ERC20("Reputation", "REP") {
stakedAgainst += s.amount;
}
}
// Special case for early evaluation if dao.totalSupply has been staked
require(
block.timestamp > pool.endTime ||
stakedFor + stakedAgainst == totalSupply(),
"Pool end time has not yet arrived"
);
// Check that quorum is met
if (
pool.params.quorum[1] * (stakedFor + stakedAgainst) <=
@ -202,19 +204,13 @@ contract DAO is ERC20("Reputation", "REP") {
emit ValidationPoolResolved(poolIndex, false, false);
// Callback if requested
if (pool.callbackOnValidate) {
try
IOnValidate(pool.sender).onValidate(
votePasses,
false,
stakedFor,
stakedAgainst,
pool.callbackData
)
{
console.log("callbackOnValidate succeed");
} catch Error(string memory reason) {
console.log("callbackOnValidate failed:", reason);
}
IOnValidate(pool.sender).onValidate(
votePasses,
false,
stakedFor,
stakedAgainst,
pool.callbackData
);
}
return false;
@ -281,23 +277,18 @@ contract DAO is ERC20("Reputation", "REP") {
address member = members[i];
uint256 share = (pool.fee * balanceOf(member)) / totalSupply();
// TODO: For efficiency this could be modified to hold the funds for recipients to withdraw
// TODO: Exclude encumbered reputation from totalSupply
payable(member).transfer(share);
}
// Callback if requested
if (pool.callbackOnValidate) {
try
IOnValidate(pool.sender).onValidate(
votePasses,
true,
stakedFor,
stakedAgainst,
pool.callbackData
)
{
console.log("callbackOnValidate succeed");
} catch Error(string memory reason) {
console.log("callbackOnValidate failed:", reason);
}
IOnValidate(pool.sender).onValidate(
votePasses,
true,
stakedFor,
stakedAgainst,
pool.callbackData
);
}
}

View File

@ -6,5 +6,5 @@ interface IOnProposalAccepted {
uint stakedFor,
uint stakedAgainst,
bytes calldata callbackData
) external;
) external returns (uint);
}

View File

@ -8,5 +8,5 @@ interface IOnValidate {
uint stakedFor,
uint stakedAgainst,
bytes calldata callbackData
) external;
) external returns (uint);
}

View File

@ -51,7 +51,7 @@ contract Onboarding is WorkContract, IOnValidate {
uint,
uint,
bytes calldata callbackData
) external {
) external returns (uint) {
require(
msg.sender == address(dao),
"onValidate may only be called by the DAO contract"
@ -61,7 +61,7 @@ contract Onboarding is WorkContract, IOnValidate {
if (!votePasses || !quorumMet) {
// refund the customer the remaining amount
payable(request.customer).transfer(request.fee / 10);
return;
return 1;
}
uint postIndex = dao.addPost(
request.customer,
@ -77,5 +77,6 @@ contract Onboarding is WorkContract, IOnValidate {
false,
""
);
return 0;
}
}

View File

@ -3,6 +3,7 @@ pragma solidity ^0.8.24;
import "./DAO.sol";
import "./IOnValidate.sol";
import "./IOnProposalAccepted.sol";
import "hardhat/console.sol";
@ -43,7 +44,7 @@ contract Proposals is DAOContract, IOnValidate {
mapping(address => uint) attestations;
uint attestationTotal;
Referendum[3] referenda;
bool callbackOnValidate;
bool callbackOnAccepted;
bytes callbackData;
}
@ -62,26 +63,26 @@ contract Proposals is DAOContract, IOnValidate {
function propose(
string calldata contentId,
uint referendum0Duration,
uint referendum1Duration,
uint referendum100Duration,
bool callbackOnValidate,
address author,
uint[3] calldata durations,
bool callbackOnAccepted,
bytes calldata callbackData
) external payable returns (uint proposalIndex) {
// TODO: Consider taking author as a parameter,
// or else accepting a postIndex instead of contentId,
// or support post lookup by contentId
uint postIndex = dao.addPost(msg.sender, contentId);
uint postIndex = dao.addPost(author, contentId);
proposalIndex = proposalCount++;
Proposal storage proposal = proposals[proposalIndex];
proposal.sender = msg.sender;
proposal.postIndex = postIndex;
proposal.startTime = block.timestamp;
proposal.referenda[0].duration = referendum0Duration;
proposal.referenda[1].duration = referendum1Duration;
proposal.referenda[2].duration = referendum100Duration;
proposal.referenda[0].duration = durations[0];
proposal.referenda[1].duration = durations[1];
proposal.referenda[2].duration = durations[2];
proposal.fee = msg.value;
proposal.remainingFee = proposal.fee;
proposal.callbackOnValidate = callbackOnValidate;
proposal.callbackOnAccepted = callbackOnAccepted;
proposal.callbackData = callbackData;
emit NewProposal(proposalIndex);
}
@ -157,7 +158,7 @@ contract Proposals is DAOContract, IOnValidate {
uint stakedFor,
uint stakedAgainst,
bytes calldata callbackData
) external {
) external returns (uint) {
require(
msg.sender == address(dao),
"onValidate may only be called by the DAO contract"
@ -181,7 +182,7 @@ contract Proposals is DAOContract, IOnValidate {
proposal.stage = Stage.Failed;
emit ProposalFailed(proposalIndex, "Quorum not met");
proposal.remainingFee += fee;
return;
return 1;
}
// Participation threshold of 50%
@ -214,33 +215,19 @@ contract Proposals is DAOContract, IOnValidate {
// Handle Referendum 100%
} else if (proposal.stage == Stage.Referendum100) {
require(referendumIndex == 2, "Stage 2 index mismatch");
// Note that no retries are attempted for referendum 100%
if (votePasses && participationAboveThreshold) {
// The proposal has passed all referenda and should become "law"
proposal.stage = Stage.Accepted;
// This is an opportunity for some actions to occur
// We should at least emit an event
// Emit an event
emit ProposalAccepted(proposalIndex);
// We also execute a callback, if requested
if (proposal.callbackOnValidate) {
try
// Note: We're directly reusing the onValidate hook we established for valdiation pools.
// if any contracts want to use both callbacks, distinct interfaces should be defined.
IOnValidate(proposal.sender).onValidate(
votePasses,
false,
stakedFor,
stakedAgainst,
proposal.callbackData
)
{
console.log("proposal callbackOnValidate succeed");
} catch Error(string memory reason) {
console.log(
"proposal callbackOnValidate failed:",
reason
);
}
// Execute a callback, if requested
if (proposal.callbackOnAccepted) {
IOnProposalAccepted(proposal.sender).onProposalAccepted(
stakedFor,
stakedAgainst,
proposal.callbackData
);
}
} else if (referendum.retryCount >= 2) {
proposal.stage = Stage.Failed;
@ -256,6 +243,7 @@ contract Proposals is DAOContract, IOnValidate {
} else if (proposal.stage == Stage.Referendum100) {
initiateValidationPool(proposalIndex, 2, proposal.fee / 10);
}
return 0;
}
/// External function that will advance a proposal to the referendum process

View File

@ -56,6 +56,8 @@ abstract contract WorkContract is
event WorkAssigned(uint requestIndex, uint stakeIndex);
event WorkEvidenceSubmitted(uint requestIndex);
event WorkApprovalSubmitted(uint requestIndex, bool approval);
event PriceChangeProposed(uint priceProposalIndex);
event PriceChangeAccepted(uint priceProposalIndex, uint price);
constructor(
DAO dao,
@ -219,24 +221,25 @@ abstract contract WorkContract is
value: msg.value
}(
contentId,
durations[0],
durations[1],
durations[2],
msg.sender,
durations,
true,
abi.encode(priceProposalIndex)
);
emit PriceChangeProposed(priceProposalIndex);
}
function onProposalAccepted(
uint, // stakedFor
uint, // stakedAgainst
bytes calldata callbackData
) external {
) external returns (uint) {
uint priceProposalIndex = abi.decode(callbackData, (uint));
PriceProposal storage priceProposal = priceProposals[
priceProposalIndex
];
price = priceProposal.price;
// TODO: Emit price change event
emit PriceChangeAccepted(priceProposalIndex, price);
return 0;
}
}

View File

@ -21,13 +21,14 @@ const fetchPost = async (postIndex) => {
const {
id, sender, author, contentId,
} = await dao.posts(postIndex);
const { content } = await readFromApi(contentId);
const { content, embeddedData } = await readFromApi(contentId);
const post = {
id,
sender,
author,
contentId,
content,
embeddedData,
};
posts[postIndex] = post;
return post;
@ -78,7 +79,7 @@ const poolIsActive = (pool) => {
return true;
};
const poolIsValid = (pool) => {
const poolIsValidWorkContract = (pool) => {
switch (pool.sender) {
case getContractAddressByNetworkName(network, 'Work1'): {
// If this is a valid work evidence
@ -93,11 +94,12 @@ const poolIsValid = (pool) => {
return pool.post.content.startsWith(expectedContent);
}
default:
console.log('Unrecognized sender %s', pool.sender);
return false;
}
};
const poolIsProposal = (pool) => pool.sender === getContractAddressByNetworkName(network, 'Proposals');
const getPoolStatus = (pool) => {
if (poolIsActive(pool)) return 'Active';
if (!pool.resolved) return 'Ready to Evaluate';
@ -111,20 +113,34 @@ const stake = async (pool, amount, inFavor) => {
await fetchReputation();
};
const stakeEach = async (pools, amountPerPool) => {
const conditionalStake = async (pool, amountToStake) => {
if (poolIsValidWorkContract(pool)) {
await stake(pool, amountToStake, true);
} else if (poolIsProposal(pool)) {
// We leave these alone at the moment.
// We could consider automatic followup staking,
// as a convenience if you decide early to favor a proposal
} else {
console.log('Unrecognized sender %s', pool.sender);
await stake(pool, amountToStake, false);
}
};
const conditionalStakeEach = async (pools, amountPerPool) => {
const promises = [];
pools.forEach(async (pool) => {
const inFavor = await poolIsValid(pool);
promises.push(stake(pool, amountPerPool, inFavor));
promises.push(conditionalStake(pool, amountPerPool));
});
await Promise.all(promises);
};
const printPool = (pool) => {
console.log(`pool ${pool.id.toString()}, `
+ `status: ${getPoolStatus(pool)}, `
+ `is valid: ${poolIsValid(pool)}, `
+ `post content: ${pool.post?.content}`);
const dataStr = pool.post?.embeddedData ? `data: ${JSON.stringify(pool.post.embeddedData)},\n ` : '';
console.log(`pool ${pool.id.toString()}\n `
+ `status: ${getPoolStatus(pool)},\n `
+ `is valid work contract: ${poolIsValidWorkContract(pool)},\n `
+ `is proposal: ${poolIsProposal(pool)},\n `
+ `${dataStr}post content: ${pool.post?.content}`);
};
async function main() {
@ -136,7 +152,7 @@ async function main() {
const activePools = validationPools.filter(poolIsActive);
if (activePools.length && reputation > 0) {
const amountPerPool = reputation / BigInt(2) / BigInt(activePools.length);
await stakeEach(activePools, amountPerPool);
await conditionalStakeEach(activePools, amountPerPool);
}
// Listen for new validation pools
@ -146,8 +162,7 @@ async function main() {
await fetchReputation();
if (!reputation) return;
const amountToStake = reputation / BigInt(2);
const inFavor = await poolIsValid(pool);
await stake(pool, amountToStake, inFavor);
await conditionalStake(pool, amountToStake);
});
dao.on('ValidationPoolResolved', async (poolIndex, votePasses) => {

View File

@ -16,20 +16,35 @@ const readFromApi = async (hash) => {
ca: readFileSync(caPath),
});
}
const { data: { author, content, signature } } = await axios.get(`${apiUrl}/read/${hash}`, options);
const {
data: {
author, content, signature, embeddedData,
},
} = await axios.get(`${apiUrl}/read/${hash}`, options);
// Verify hash
const derivedHash = objectHash({ author, content, signature });
const derivedHash = objectHash({
author, content, signature, embeddedData,
});
if (derivedHash !== hash) {
throw new Error('hash mismatch');
}
// Verify embedded data
let contentToVerify = content;
if (embeddedData && Object.entries(embeddedData).length) {
contentToVerify += `\n\n${JSON.stringify(embeddedData, null, 2)}`;
}
// Verify signature
const account = recoverPersonalSignature({ data: content, signature });
const account = recoverPersonalSignature({ data: contentToVerify, signature });
if (account !== author) {
throw new Error('author does not match signature');
}
return { author, content, signature };
return {
author, content, signature, embeddedData,
};
};
module.exports = readFromApi;

View File

@ -167,8 +167,19 @@ describe('DAO', () => {
});
describe('Evaluate outcome', () => {
it('should not be able to evaluate outcome before duration has elapsed', async () => {
await expect(dao.evaluateOutcome(0)).to.be.revertedWith('Pool end time has not yet arrived');
it('should not be able to evaluate outcome before duration has elapsed if not all rep has been staked', async () => {
time.increase(POOL_DURATION + 1);
await expect(dao.evaluateOutcome(0));
await initiateValidationPool({ fee: 100 });
await expect(dao.evaluateOutcome(1)).to.be.revertedWith('Pool end time has not yet arrived');
});
it('should not be able to evaluate outcome before duration has elapsed unless all rep has been staked', async () => {
time.increase(POOL_DURATION + 1);
await expect(dao.evaluateOutcome(0));
await initiateValidationPool({ fee: 100 });
await dao.stake(1, 100, true);
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true, true);
});
it('should be able to evaluate outcome after duration has elapsed', async () => {

View File

@ -14,8 +14,10 @@ describe('Onboarding', () => {
const DAO = await ethers.getContractFactory('DAO');
const dao = await DAO.deploy();
const Proposals = await ethers.getContractFactory('Proposals');
const proposals = await Proposals.deploy(dao.target);
const Onboarding = await ethers.getContractFactory('Onboarding');
const onboarding = await Onboarding.deploy(dao.target, PRICE);
const onboarding = await Onboarding.deploy(dao.target, proposals.target, PRICE);
await dao.addPost(account1, 'content-id');
const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);

View File

@ -79,7 +79,7 @@ describe('Proposal', () => {
} = await loadFixture(deploy));
const emptyCallbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
await proposals.propose('proposal-content-id', 20, 20, 20, false, emptyCallbackData, { value: 100 });
await proposals.propose('proposal-content-id', account1, [20, 20, 20], false, emptyCallbackData, { value: 100 });
expect(await proposals.proposalCount()).to.equal(1);
proposal = await proposals.proposals(0);
expect(proposal.postIndex).to.equal(2);