diff --git a/daos/private/testdao37.json b/daos/private/testdao37.json new file mode 100644 index 00000000..b9c98d26 --- /dev/null +++ b/daos/private/testdao37.json @@ -0,0 +1,39 @@ +{ + "name": "Hubristic Colorist", + "Avatar": "0x66301c44C22E0AEcd8ee8e6073A8300dC138A7af", + "DAOToken": "0xa6b58AE81c37b45BbeAC4C86866fA4b48e378139", + "Reputation": "0xB9e9DfAcb27f42357215aC3942810786C0cb75e2", + "Controller": "0xf577703d169b02e6807f80a4fCe029f7F91124CB", + "Schemes": [ + { + "name": "GenericScheme", + "alias": "GenericSchemeAlias", + "address": "0xCE792fA3A9cd660fFD1be077CB1358A08Cf9106B", + "arcVersion": "0.0.1-rc.37" + }, + { + "name": "ContributionRewardExt", + "alias": "ContributionRewardExt", + "address": "0xA10f1c26d7D4BCB1a93F84652177D2E2b320378B", + "arcVersion": "0.0.1-rc.36" + } + ], + "StandAloneContracts": [ + { + "name": "Wallet", + "address": "0x0E1598278e34793C83A4B1989d410DD0D7652b92", + "arcVersion": "0.0.1-rc.37" + }, + { + "name": "ContributionRewardExt", + "address": "0xA10f1c26d7D4BCB1a93F84652177D2E2b320378B", + "arcVersion": "0.0.1-rc.36" + }, + { + "name": "Competition", + "address": "0xD65d9Cb0fC54c95Ed021D74cB5164b5B30bB4B86", + "arcVersion": "0.0.1-rc.36" + } + ], + "arcVersion": "0.0.1-rc.37" +} \ No newline at end of file diff --git a/daos/rinkeby/devtest.json b/daos/rinkeby/devtest.json new file mode 100644 index 00000000..e82bb546 --- /dev/null +++ b/daos/rinkeby/devtest.json @@ -0,0 +1,15 @@ +{ + "name": "DevTest", + "Avatar": "0xBeB9C86C8D54e5C7cA2a7Dc67E50419FB98D9c05", + "DAOToken": "0x57bae8c98F913c10bBC23342596dE2E58E45466D", + "Reputation": "0x57bE0441440dFA2faE4Dd80Ec355Bc6db8257563", + "Controller": "0xB460481416B688b7E1c0D407f033567C687Fb607", + "Schemes": [ + { + "name": "GenericScheme", + "alias": "GenericSchemeAlias", + "address": "0xad96d9685783bF29cB31eB038A44eC808aC17265" + } + ], + "arcVersion": "0.0.1-rc.31" + } \ No newline at end of file diff --git a/ops/mappings.json b/ops/mappings.json index 9a14a6ee..36824e64 100644 --- a/ops/mappings.json +++ b/ops/mappings.json @@ -8,6 +8,13 @@ "mapping": "DAOToken", "arcVersion": "0.0.1-rc.16" }, + { + "name": "NewSignalScheme", + "dao": "address", + "mapping": "SignalScheme", + "arcVersion": "0.0.1-rc.37", + "address": "0x383A20B3635a10a4da8f733E8eA256cBcAcB4D4E" + }, { "name": "GenesisProtocol", "contractName": "GenesisProtocol", @@ -737,6 +744,13 @@ "mapping": "DAOToken", "arcVersion": "0.0.1-rc.19" }, + { + "name": "NewSignalScheme", + "dao": "address", + "mapping": "SignalScheme", + "arcVersion": "0.0.1-rc.37", + "address": "0xE06d896da40B73Cb02650220E480e20F38e7bE18" + }, { "name": "Reputation", "contractName": "Reputation", diff --git a/src/domain/index.ts b/src/domain/index.ts index 1a34ceb4..3de80e26 100644 --- a/src/domain/index.ts +++ b/src/domain/index.ts @@ -1,4 +1,4 @@ -import { Address, BigDecimal, BigInt, Bytes, Entity, store, Value} from '@graphprotocol/graph-ts'; +import { Address, BigDecimal, BigInt, Bytes, Entity, store, Value } from '@graphprotocol/graph-ts'; import { setContractsInfo, setTemplatesInfo } from '../contractsInfo'; import { Transfer } from '../types/DAOToken/DAOToken'; import { @@ -36,22 +36,23 @@ import { import { daoBountyRedemption, insertGPRewards, - insertGPRewardsToHelper , - reputationRedemption , + insertGPRewardsToHelper, + reputationRedemption, tokenRedemption, } from './reward'; +import { writeSignal } from './signal'; import { insertStake } from './stake'; import { getToken, insertToken, updateTokenTotalSupply } from './token'; import { insertVote } from './vote'; -function isProposalValid(proposalId: string ): boolean { +function isProposalValid(proposalId: string): boolean { let p = Proposal.load(proposalId); - return ((p != null) && (equalsBytes(p.paramsHash, new Bytes(32)) == false)); + return ((p != null) && (equalsBytes(p.paramsHash, new Bytes(32)) == false)); } function handleGPProposalPrivate(proposalId: string): void { - let gpProposal = GenesisProtocolProposal.load(proposalId); - if (gpProposal != null) { + let gpProposal = GenesisProtocolProposal.load(proposalId); + if (gpProposal != null) { updateGPProposal( gpProposal.address as Address, gpProposal.proposalId, @@ -66,7 +67,7 @@ function handleGPProposalPrivate(proposalId: string): void { 3, // Queued gpProposal.address as Address, ); - } + } } export function handleNewContributionProposal( @@ -92,26 +93,26 @@ export function handleNewContributionProposal( } export function handleNewSchemeRegisterProposal( - proposalId: string, - timestamp: BigInt, - avatar: Bytes, - votingMachine: Bytes, - descriptionHash: string, - schemeAddress: Address, - ): void { - if (!daoModule.exists(avatar as Address)) { - return; - } - updateSRProposal( - proposalId, - timestamp, - avatar as Address, - votingMachine as Address, - descriptionHash, - schemeAddress, - ); - handleGPProposalPrivate(proposalId); - } + proposalId: string, + timestamp: BigInt, + avatar: Bytes, + votingMachine: Bytes, + descriptionHash: string, + schemeAddress: Address, +): void { + if (!daoModule.exists(avatar as Address)) { + return; + } + updateSRProposal( + proposalId, + timestamp, + avatar as Address, + votingMachine as Address, + descriptionHash, + schemeAddress, + ); + handleGPProposalPrivate(proposalId); +} export function handleNewCallProposal( avatar: Address, @@ -138,12 +139,12 @@ export function handleStake(event: Stake): void { if (equalsBytes(proposal.paramsHash, new Bytes(32))) { return; } - if (event.params._vote.toI32() == 1) { + if (event.params._vote.toI32() == 1) { proposal.stakesFor = proposal.stakesFor.plus(event.params._amount); } else { proposal.stakesAgainst = proposal.stakesAgainst.plus(event.params._amount); } - proposal.confidence = (new BigDecimal(proposal.stakesFor)) / (new BigDecimal(proposal.stakesAgainst)); + proposal.confidence = (new BigDecimal(proposal.stakesFor)) / (new BigDecimal(proposal.stakesAgainst)); saveProposal(proposal); insertStake( eventId(event), @@ -192,16 +193,12 @@ export function handleVoteProposal(event: VoteProposal): void { export function confidenceLevelUpdate(proposalId: Bytes, confidenceThreshold: BigInt): void { if (isProposalValid(proposalId.toHex())) { - updateProposalconfidence(proposalId, confidenceThreshold); + updateProposalconfidence(proposalId, confidenceThreshold); } } -export function handleRegisterScheme(avatar: Address, - nativeTokenAddress: Address, - nativeReputationAddress: Address, - scheme: Address , - paramsHash: Bytes, - timestamp: BigInt): void { +export function handleRegisterScheme(avatar: Address, nativeTokenAddress: Address, nativeReputationAddress: Address, + scheme: Address, paramsHash: Bytes, timestamp: BigInt): void { // Detect the first register scheme event which indicates a new DAO let isFirstRegister = store.get( 'FirstRegisterSchemeFlag', @@ -209,7 +206,7 @@ export function handleRegisterScheme(avatar: Address, ); if (isFirstRegister == null) { setContractsInfo(); - let dao = daoModule.insertNewDAO(avatar, nativeTokenAddress , nativeReputationAddress); + let dao = daoModule.insertNewDAO(avatar, nativeTokenAddress, nativeReputationAddress); insertToken(hexToAddress(dao.nativeToken), avatar.toHex()); insertReputation( hexToAddress(dao.nativeReputation), @@ -265,20 +262,20 @@ export function handleNativeTokenTransfer(event: Transfer): void { } export function handleExecuteProposal(event: ExecuteProposal): void { - if (isProposalValid(event.params._proposalId.toHex())) { - updateProposalExecution(event.params._proposalId, event.params._totalReputation, event.block.timestamp); - } + if (isProposalValid(event.params._proposalId.toHex())) { + updateProposalExecution(event.params._proposalId, event.params._totalReputation, event.block.timestamp); + } } export function handleStateChange(event: StateChange): void { if (isProposalValid(event.params._proposalId.toHex())) { - updateProposalState(event.params._proposalId, event.params._proposalState, event.address); - if ((event.params._proposalState == 1) || - (event.params._proposalState == 2)) { - insertGPRewards(event.params._proposalId, event.block.timestamp, event.address, event.params._proposalState); - } + updateProposalState(event.params._proposalId, event.params._proposalState, event.address); + if ((event.params._proposalState == 1) || + (event.params._proposalState == 2)) { + insertGPRewards(event.params._proposalId, event.block.timestamp, event.address, event.params._proposalState); + } - addProposalStateChangeEvent(event.params._proposalId, event.transaction.from, event.block.timestamp); + addProposalStateChangeEvent(event.params._proposalId, event.transaction.from, event.block.timestamp); } } @@ -288,20 +285,20 @@ export function handleExecutionStateChange(event: GPExecuteProposal): void { } } -export function handleGPRedemption(proposalId: Bytes, beneficiary: Address , timestamp: BigInt , type: string): void { - if (isProposalValid(proposalId.toHex())) { - if (type == 'token') { - tokenRedemption(proposalId, beneficiary, timestamp); - } else if (type == 'reputation') { - reputationRedemption(proposalId, beneficiary, timestamp); - } else { - daoBountyRedemption(proposalId, beneficiary, timestamp); - } +export function handleGPRedemption(proposalId: Bytes, beneficiary: Address, timestamp: BigInt, type: string): void { + if (isProposalValid(proposalId.toHex())) { + if (type == 'token') { + tokenRedemption(proposalId, beneficiary, timestamp); + } else if (type == 'reputation') { + reputationRedemption(proposalId, beneficiary, timestamp); + } else { + daoBountyRedemption(proposalId, beneficiary, timestamp); } + } } export function daoRegister(dao: Address, tag: string): void { - daoModule.register(dao, tag); + daoModule.register(dao, tag); } export function addDaoMember(reputationHolder: ReputationHolder): void { @@ -321,10 +318,14 @@ export function addDaoMember(reputationHolder: ReputationHolder): void { } export function removeDaoMember(reputationHolder: ReputationHolder): void { - let dao = getReputation(reputationHolder.contract.toHex()).dao; - if (dao == null) { - // reputation that's not attached to a DAO - return; - } - daoModule.decreaseDAOmembersCount(dao); + let dao = getReputation(reputationHolder.contract.toHex()).dao; + if (dao == null) { + // reputation that's not attached to a DAO + return; + } + daoModule.decreaseDAOmembersCount(dao); +} + +export function addSignal(signalId: string, proposalId: string): void { + writeSignal(signalId, proposalId); } diff --git a/src/domain/schema.graphql b/src/domain/schema.graphql index b6b660c1..3d3c3a0b 100644 --- a/src/domain/schema.graphql +++ b/src/domain/schema.graphql @@ -211,3 +211,8 @@ type Event @entity { dao: DAO timestamp: BigInt! } + +type Signal @entity{ + id: ID! + data: String +} diff --git a/src/domain/signal.ts b/src/domain/signal.ts new file mode 100644 index 00000000..e15f2f56 --- /dev/null +++ b/src/domain/signal.ts @@ -0,0 +1,101 @@ +import { Bytes, ipfs, json, JSONValueKind, store } from '@graphprotocol/graph-ts'; +import { Signal } from '../types/schema'; +import { debug } from '../utils'; + +function getSignal(id: string): Signal { + let sig = store.get('Signal', id) as Signal; + if (!sig) { + sig = new Signal(id); + } + return sig; +} + +export function writeSignal(signalId: string, proposalIpfsId: string): void { + readProposal(signalId, proposalIpfsId); +} + +function generateString(key: string, value: string, signal: Signal): void { + let curlybraceopen = '{'; + let curlybraceclose = '}'; + let colon = ':'; + let comma = ','; + let quotation = '"'; + let newkeypair = quotation.concat(key).concat(quotation).concat(colon) + .concat(quotation).concat(value).concat(quotation); + debug(newkeypair); + if (signal.data == null) { + debug('Going-in-Generate'); + generateNewDict(newkeypair, signal, curlybraceopen, curlybraceclose); + } else { + debug('In-Else'); + debug(key); + let searchstringposition = signal.data.indexOf(key); + debug(searchstringposition.toString()); + if (searchstringposition === -1) { + appendDict(newkeypair, signal, curlybraceclose, comma); + } else if (key !== '') { + replaceInDict(newkeypair, key, signal, curlybraceopen, curlybraceclose, comma); + } + } +} + +function replaceInDict(newkeypair: string, key: string, signal: Signal, + curlybraceopen: string, curlybraceclosed: string, + comma: string): void { + debug('In-Replace'); + let length = signal.data.length; + let cutstring = signal.data.slice(1, length - 1); + let stringarray = cutstring.split(comma); + let finalstring = ''; + for (let y = 0; y < stringarray.length; ++y) { + if (stringarray[y].indexOf(key) === -1) { + finalstring = finalstring.concat(comma).concat(stringarray[y]); + } else { + finalstring = finalstring.concat(comma).concat(newkeypair); + } + } + signal.data = curlybraceopen.concat(finalstring.slice(1)).concat(curlybraceclosed); + debug(signal.data); + debug('replace'); + signal.save(); +} + +function generateNewDict(keypair: string, signal: Signal, curlybraceopen: string, curlybraceclosed: string): void { + let data = curlybraceopen.concat(keypair).concat(curlybraceclosed); + debug('generate '); + signal.data = data; + signal.save(); +} + +function appendDict(keypair: string, signal: Signal, curlybraceclosed: string, comma: string): void { + debug('In-Append'); + let signalsubstring = signal.data.substring(0, signal.data.length - 1); + signal.data = signalsubstring.concat(comma).concat(keypair).concat(curlybraceclosed); + debug(signal.data); + signal.save(); +} + +function readProposal(id: string, proposalIpfsId: string): void { + let signal = getSignal(id); + let key = ''; + let value = ''; + let ipfsData = ipfs.cat('/ipfs/' + proposalIpfsId); + debug('Proposal ID ' + proposalIpfsId); + if (ipfsData != null && ipfsData.toString() !== '{}') { + + let descJson = json.fromBytes(ipfsData as Bytes); + if (descJson.kind !== JSONValueKind.OBJECT) { + debug('No JSON'); + } + if (descJson.toObject().get('key') != null) { + key = descJson.toObject().get('key').toString(); + debug(key); + } + if (descJson.toObject().get('value') != null) { + value = descJson.toObject().get('value').toString(); + debug(value); + } + + } + generateString(key, value, signal); +} diff --git a/src/mappings/GenericScheme/mapping.ts b/src/mappings/GenericScheme/mapping.ts index a137927c..c0adb168 100644 --- a/src/mappings/GenericScheme/mapping.ts +++ b/src/mappings/GenericScheme/mapping.ts @@ -1,4 +1,5 @@ import { store } from '@graphprotocol/graph-ts'; +import { debug } from '../../utils'; // Import event types from the Reputation contract ABI import { diff --git a/src/mappings/SignalScheme/datasource.yaml b/src/mappings/SignalScheme/datasource.yaml new file mode 100644 index 00000000..dc3eb202 --- /dev/null +++ b/src/mappings/SignalScheme/datasource.yaml @@ -0,0 +1,6 @@ +abis: + - NewSignalScheme + +eventHandlers: + - event: SignalLog(indexed address,string) + handler: handleSignal diff --git a/src/mappings/SignalScheme/mapping.ts b/src/mappings/SignalScheme/mapping.ts new file mode 100644 index 00000000..73cc2a6b --- /dev/null +++ b/src/mappings/SignalScheme/mapping.ts @@ -0,0 +1,8 @@ +import { addSignal } from '../../domain'; +import { SignalLog } from '../../types/NewSignalScheme/NewSignalScheme'; + +export function handleSignal( event: SignalLog): void { + let signalId = event.params._sender.toHex(); + let proposalId = event.params._descriptionHash; + addSignal(signalId, proposalId); +} diff --git a/src/mappings/SignalScheme/schema.graphql b/src/mappings/SignalScheme/schema.graphql new file mode 100644 index 00000000..e69de29b diff --git a/src/mappings/UGenericScheme/mapping.ts b/src/mappings/UGenericScheme/mapping.ts index b057263a..a54c5728 100644 --- a/src/mappings/UGenericScheme/mapping.ts +++ b/src/mappings/UGenericScheme/mapping.ts @@ -49,5 +49,9 @@ export function handleProposalExecuted( ent.returnValue = event.params._genericCallReturnValue; } + let signalId = event.params._avatar.toHex(); + let proposalId = event.params._proposalId.toHex(); + domain.addSignal(signalId, proposalId); + store.set('GenericSchemeProposal', event.params._proposalId.toHex(), ent); } diff --git a/test/0.0.1-rc.16/UGenericSchemeSignal.spec.ts b/test/0.0.1-rc.16/UGenericSchemeSignal.spec.ts new file mode 100644 index 00000000..36eb4b76 --- /dev/null +++ b/test/0.0.1-rc.16/UGenericSchemeSignal.spec.ts @@ -0,0 +1,278 @@ +import { + getArcVersion, + getContractAddresses, + getOptions, + getWeb3, + sendQuery, + waitUntilTrue, + writeProposalIPFS, +} from './util'; + +jest.setTimeout(30000); + +const ActionMock = require('@daostack/migration/contracts/' + getArcVersion() + '/ActionMock.json'); +const GenericScheme = require('@daostack/migration/contracts/' + getArcVersion() + '/GenericScheme.json'); +const GenesisProtocol = require('@daostack/migration/contracts/' + getArcVersion() + '/GenesisProtocol.json'); + +const maintest = async (web3, addresses, opts, proposalIPFSData, matchto) => { + const accounts = web3.eth.accounts.wallet; + + const genericScheme = new web3.eth.Contract( + GenericScheme.abi, + addresses.GenericScheme, + opts, + ); + const genesisProtocol = new web3.eth.Contract( + GenesisProtocol.abi, + addresses.GenesisProtocol, + opts, + ); + + const actionMock = new web3.eth.Contract( + ActionMock.abi, + addresses.ActionMock, + opts, + ); + + let proposalDescription = proposalIPFSData.description; + let proposalTitle = proposalIPFSData.title; + let proposalUrl = proposalIPFSData.url; + + let descHash = await writeProposalIPFS(proposalIPFSData); + let callData = await actionMock.methods.test2(addresses.TestAvatar).encodeABI(); + + async function propose() { + const prop = genericScheme.methods.proposeCall(addresses.TestAvatar, callData, 0, descHash); + const proposalId = await prop.call(); + const { blockNumber } = await prop.send(); + const { timestamp } = await web3.eth.getBlock(blockNumber); + return { proposalId, timestamp }; + } + + const [PASS, FAIL] = [1, 2]; + async function vote({ proposalId, outcome, voter, amount = 0 }) { + const { blockNumber } = await genesisProtocol.methods + .vote(proposalId, outcome, amount, voter) + .send({ from: voter }); + const { timestamp } = await web3.eth.getBlock(blockNumber); + return timestamp; + } + + const { proposalId: p1, timestamp: p1Creation } = await propose(); + // console.log(p1) + const getProposal = `{ + proposal(id: "${p1}") { + id + descriptionHash + stage + createdAt + executedAt + proposer + votingMachine + + genericScheme { + id + dao { + id + } + contractToCall + callData + value + executed + returnValue + } + scheme { + uGenericSchemeParams { + contractToCall + } + } + } +}`; + + let proposal = (await sendQuery(getProposal, 90000)).proposal; + expect(proposal).toMatchObject({ + id: p1, + descriptionHash: descHash, + stage: 'Queued', + createdAt: p1Creation.toString(), + executedAt: null, + proposer: web3.eth.defaultAccount.toLowerCase(), + votingMachine: genesisProtocol.options.address.toLowerCase(), + + genericScheme: { + id: p1, + dao: { + id: addresses.TestAvatar.toLowerCase(), + }, + contractToCall: actionMock.options.address.toLowerCase(), + callData, + value: '0', + executed: false, + returnValue: null, + }, + scheme: { + uGenericSchemeParams: { + contractToCall: actionMock.options.address.toLowerCase(), + }, + }, + }); + + await vote({ + proposalId: p1, + outcome: PASS, + voter: accounts[0].address, + }); + + await vote({ + proposalId: p1, + outcome: PASS, + voter: accounts[1].address, + }); + + await vote({ + proposalId: p1, + outcome: PASS, + voter: accounts[2].address, + }); + + let executedAt = await vote({ + proposalId: p1, + outcome: PASS, + voter: accounts[3].address, + }); + + const executedIsIndexed = async () => { + return (await sendQuery(getProposal)).proposal.executedAt != null; + }; + + await waitUntilTrue(executedIsIndexed); + + proposal = (await sendQuery(getProposal)).proposal; + expect(proposal).toMatchObject({ + id: p1, + descriptionHash: descHash, + stage: 'Executed', + createdAt: p1Creation.toString(), + executedAt: executedAt + '', + proposer: web3.eth.defaultAccount.toLowerCase(), + votingMachine: genesisProtocol.options.address.toLowerCase(), + + genericScheme: { + id: p1, + dao: { + id: addresses.TestAvatar.toLowerCase(), + }, + contractToCall: actionMock.options.address.toLowerCase(), + callData, + value: '0', + executed: true, + returnValue: '0x0000000000000000000000000000000000000000000000000000000000000001', + }, + }); + + const metaq = `{ + signals{ + id + data + } + }`; + + // const metaq = `{ + // debugs{ + // id + // message + // } + // }` + + const metadata = await sendQuery(metaq, 15000); + expect(metadata).toMatchObject(matchto); + +}; + +describe('Generic Signal Scheme', () => { + let web3; + let addresses; + let opts; + + beforeAll(async () => { + web3 = await getWeb3(); + addresses = getContractAddresses(); + opts = await getOptions(web3); + }); + + it('generic scheme proposal generate ', async () => { + + let proposalIPFSData = { + description: 'Setting new header Image', + title: 'New Header Image', + url: 'http://swift.org/modest', + key: 'Header', + value: 'https://de.wikipedia.org/wiki/Wald#/media/Datei:Laurisilva_en_el_Cubo_de_la_Galga.jpg', + }; + + let matchto = { + signals: [ + { + data: + '{"Header":"https://de.wikipedia.org/wiki/Wald#/media/Datei:Laurisilva_en_el_Cubo_de_la_Galga.jpg"}', + id: + '0x86e9fe552e75e4fc51f46e4efc128628ecd5ada7', + }, + ], + }; + + const test = await maintest(web3, addresses, opts, proposalIPFSData, matchto); + + }, 200000); + + it('generic scheme proposal append ', async () => { + + let proposalIPFSData = { + description: 'Setting new Icon', + title: 'New Icon', + url: 'http://swift.org/modest', + key: 'Icon', + value: 'https://en.wikipedia.org/wiki/River#/media/File:Melting_Toe_of_Athabasca_Glacier.jpg', + }; + + let matchto = { + signals: [ + { + data: + '{"Header":"https://de.wikipedia.org/wiki/Wald#/media/Datei:Laurisilva_en_el_Cubo_de_la_Galga.jpg","Icon":"https://en.wikipedia.org/wiki/River#/media/File:Melting_Toe_of_Athabasca_Glacier.jpg"}', + id: + '0x86e9fe552e75e4fc51f46e4efc128628ecd5ada7', + }, + ], + }; + + const test = await maintest(web3, addresses, opts, proposalIPFSData, matchto); + + }, 200000); + + it('generic scheme proposal replace ', async () => { + + let proposalIPFSData = { + description: 'Replace the Header', + title: 'New Header Image', + url: 'http://swift.org/modest', + key: 'Header', + value: 'https://de.wikipedia.org/wiki/Wasserfall#/media/Datei:Russell_Falls_2.jpg', + }; + + let matchto = { + signals: [ + { + data: + '{"Header":"https://de.wikipedia.org/wiki/Wasserfall#/media/Datei:Russell_Falls_2.jpg","Icon":"https://en.wikipedia.org/wiki/River#/media/File:Melting_Toe_of_Athabasca_Glacier.jpg"}', + id: + '0x86e9fe552e75e4fc51f46e4efc128628ecd5ada7', + }, + ], + }; + + const test = await maintest(web3, addresses, opts, proposalIPFSData, matchto); + + }, 200000); + +}); diff --git a/test/0.0.1-rc.33/SignalSchemeABI.json b/test/0.0.1-rc.33/SignalSchemeABI.json new file mode 100644 index 00000000..8d45dbb1 --- /dev/null +++ b/test/0.0.1-rc.33/SignalSchemeABI.json @@ -0,0 +1,39 @@ +{ + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "_descriptionHash", + "type": "string" + } + ], + "name": "SignalLog", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "_descriptionHash", + "type": "string" + } + ], + "name": "signal", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b5061015c806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f613f16114610030575b600080fd5b6100a76004803603602081101561004657600080fd5b810190808035906020019064010000000081111561006357600080fd5b82018360208201111561007557600080fd5b8035906020019184600183028401116401000000008311171561009757600080fd5b90919293919293905050506100a9565b005b3373ffffffffffffffffffffffffffffffffffffffff167fc5e5dee32f45752fe891f97ae70ae8ea7351f3c7d67fa09275a2dc99be92559e838360405180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405180910390a2505056fea265627a7a72315820bb4b2ec14fe5331a03bfc3928923da8afc866df6c3e3e894107f10700c0c552a64736f6c634300050b0032" +} \ No newline at end of file diff --git a/test/0.0.1-rc.33/SignalSchemeSanity.spec.ts b/test/0.0.1-rc.33/SignalSchemeSanity.spec.ts new file mode 100644 index 00000000..cdbd2934 --- /dev/null +++ b/test/0.0.1-rc.33/SignalSchemeSanity.spec.ts @@ -0,0 +1,229 @@ +import { + getArcVersion, + getContractAddresses, + getOptions, + getWeb3, + sendQuery, + writeProposalIPFS, +} from './util'; + +jest.setTimeout(30000); + +const DaoCreator = require('@daostack/migration/contracts/' + getArcVersion() + '/DaoCreator.json'); +const Avatar = require('@daostack/migration/contracts/' + getArcVersion() + '/Avatar.json'); +const GenesisProtocol = require('@daostack/migration/contracts/' + getArcVersion() + '/GenesisProtocol.json'); +const GenericScheme = require('@daostack/migration/contracts/' + getArcVersion() + '/GenericScheme.json'); + +/** + * Address and ABI of the SignalScheme Contract. + * Currently SignalScheme is deployed manually and address is added here. + * The Signal Scheme address should also be added/updated + * in ops/mappings.json (required for subgraph tracking). + * + * NOTE: This manual deployment should be done before the subgraph deployment. + * + * Technically this "Scheme" should be part of the arc and migrations package + * and these values should be derived from there. + */ +const SignalScheme = require('./SignalSchemeABI.json'); +const SignalSchemeAddress = '0x383A20B3635a10a4da8f733E8eA256cBcAcB4D4E'; + +describe('Generic Signal Scheme', () => { + let web3; + let avatar; + let genesisProtocol; + let signalScheme; + let genericScheme; + + /** + * Deploy a DAO with Generic Scheme (that has address of the SignalScheme) + * + */ + beforeAll(async () => { + web3 = await getWeb3(); + const addresses = getContractAddresses(); + const opts = await getOptions(web3); + const daoCreator = new web3.eth.Contract(DaoCreator.abi, addresses.DaoCreator, opts); + genesisProtocol = await new web3.eth.Contract(GenesisProtocol.abi, addresses.GenesisProtocol, opts); + const accounts = web3.eth.accounts.wallet; + + const gpParams = { + queuedVoteRequiredPercentage: 50, + queuedVotePeriodLimit: 60, + boostedVotePeriodLimit: 5, + preBoostedVotePeriodLimit: 0, + thresholdConst: 2000, + quietEndingPeriod: 0, + proposingRepReward: 60, + votersReputationLossRatio: 10, + minimumDaoBounty: 15, + daoBountyConst: 10, + activationTime: 0, + voteOnBehalf: '0x0000000000000000000000000000000000000000', + }; + const vmSetParams = genesisProtocol.methods.setParameters( + [ + gpParams.queuedVoteRequiredPercentage, + gpParams.queuedVotePeriodLimit, + gpParams.boostedVotePeriodLimit, + gpParams.preBoostedVotePeriodLimit, + gpParams.thresholdConst, + gpParams.quietEndingPeriod, + gpParams.proposingRepReward, + gpParams.votersReputationLossRatio, + gpParams.minimumDaoBounty, + gpParams.daoBountyConst, + gpParams.activationTime, + ], + gpParams.voteOnBehalf, + ); + const vmParamsHash = await vmSetParams.call(); + await vmSetParams.send(); + + const tx = await daoCreator.methods.forgeOrg( + 'Test DAO', + 'Test Token', + 'TST', + [accounts[0].address, accounts[1].address, accounts[2].address, accounts[3].address], + [1000, 1000, 1000, 1000], + [2000, 2000, 2000, 2000], + '0x0000000000000000000000000000000000000000', + 0, + ).send(); + + const avatarAddress = tx.events.NewOrg.returnValues._avatar; + avatar = await new web3.eth.Contract(Avatar.abi, avatarAddress, opts); + + signalScheme = new web3.eth.Contract( + SignalScheme.abi, + SignalSchemeAddress, + opts, + ); + + genericScheme = await new web3.eth.Contract( + GenericScheme.abi, + undefined, + opts, + ).deploy({ + data: GenericScheme.bytecode, + arguments: [], + }).send(); + + await daoCreator.methods.setSchemes( + avatar.options.address, + [genericScheme.options.address], + [vmParamsHash], + ['0x00000010'], + '', + ).send(); + + await genericScheme.methods.initialize( + avatar.options.address, + genesisProtocol.options.address, + vmParamsHash, + SignalSchemeAddress, + ).send(); + + }); + + it('Insert Signal Data', async () => { + + let proposalIPFSData = { + description: 'Setting new header Image', + title: 'New Header Image', + url: 'https://w.wallhaven.cc/full/13/wallhaven-13mk9v.jpg', + key: 'Header', + value: 'https://w.wallhaven.cc/full/13/wallhaven-13mk9v.jpg', + }; + + let matchto = { + signal: + { + data: + '{"Header":"https://w.wallhaven.cc/full/13/wallhaven-13mk9v.jpg"}', + id: avatar.options.address.toLowerCase(), + }, + }; + + await mainTest(web3, avatar, genericScheme, genesisProtocol, signalScheme, proposalIPFSData, matchto); + + }, 100000); + + it('Update Signal Data', async () => { + + let proposalIPFSData = { + description: 'Update new header Image', + title: 'New Header Image', + url: 'https://w.wallhaven.cc/full/14/wallhaven-13mk9v.jpg', + key: 'Header', + value: 'https://w.wallhaven.cc/full/14/wallhaven-13mk9v.jpg', + }; + + let matchto = { + signal: + { + data: + '{"Header":"https://w.wallhaven.cc/full/14/wallhaven-13mk9v.jpg"}', + id: avatar.options.address.toLowerCase(), + }, + }; + + await mainTest(web3, avatar, genericScheme, genesisProtocol, signalScheme, proposalIPFSData, matchto); + + }, 100000); + + it('Remove Signal Data', async () => { + + let proposalIPFSData = { + description: 'Remove header Image', + title: 'Remove Header Image', + url: '', + key: 'Header', + value: '', + }; + + let matchto = { + signal: + { + data: + '{"Header":""}', + id: avatar.options.address.toLowerCase(), + }, + }; + + await mainTest(web3, avatar, genericScheme, genesisProtocol, signalScheme, proposalIPFSData, matchto); + + }, 100000); + +}); + +/** + * Creates a Signal Scheme proposal and then votes on it until it is executed (>50%) + */ +const mainTest = async (web3, avatar, genericScheme, genesisProtocol, signalScheme, proposalIPFSData, matchto) => { + const accounts = web3.eth.accounts.wallet; + + const descHash = await writeProposalIPFS(proposalIPFSData); + const callData = await signalScheme.methods.signal(descHash).encodeABI(); + + const prop = genericScheme.methods.proposeCall(callData, 0, descHash); + const proposalId = await prop.call(); + await prop.send(); + await genesisProtocol.methods.vote(proposalId, 1 /** YES */, 0, accounts[0].address) + .send({ from: accounts[0].address }); + await genesisProtocol.methods.vote(proposalId, 1 /** YES */, 0, accounts[1].address) + .send({ from: accounts[1].address }); + await genesisProtocol.methods.vote(proposalId, 1 /** YES */, 0, accounts[2].address) + .send({ from: accounts[2].address }); + + const metaq = `{ + signal(id: "${avatar.options.address.toLowerCase()}"){ + id + data + } + }`; + + const metadata = await sendQuery(metaq, 5000); + expect(metadata).toMatchObject(matchto); + +};