Skip to content

Commit

Permalink
Token trade plugin added (#521)
Browse files Browse the repository at this point in the history
* [WIP] Token trade plugin added but not tested yet

* Eliminate indexed 'proposal' field on TokenTrade proposal type

* [WIP] TokenTrade Proposal tests

* Names and types tweaks

* Updated packages with new migration version

* updated test env version

* Token trade tests (haven't run yet) and type changes

* Comparison fix on trade test

* [WIP] Test missing redeem step

* tests works

* fix lint error

* removing logs

* improving tests

* [WIP] Approve tokens method

* trying to make approve work

* Token approval fix

* Build fixes

* creating token trade on test

* lint fixed

* waiting for plugin to be index

* trying to fix test

* Plugin Address fix

* test env updated

* removing old changes

* hardcoding erc20 token abi based on Oren's feedback

* tests for approve tokens created

* improving approval test

* bump version

Co-authored-by: Nestor Amesty <nestor09amesty@gmail.com>
  • Loading branch information
cbrzn and namesty committed Aug 31, 2020
1 parent b191504 commit be6b241
Show file tree
Hide file tree
Showing 12 changed files with 523 additions and 24 deletions.
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ services:
GRAPH_GRAPHQL_MAX_FIRST: '1000'

ipfs:
image: daostack/test-env-experimental-ipfs:4.0.18
image: daostack/test-env-experimental-ipfs:4.0.19
ports:
- 5001:5001

postgres:
image: daostack/test-env-experimental-postgres:4.0.18
image: daostack/test-env-experimental-postgres:4.0.19
ports:
- 9432:5432
environment:
POSTGRES_PASSWORD: 'letmein'

ganache:
image: daostack/test-env-experimental-ganache:4.0.18
image: daostack/test-env-experimental-ganache:4.0.19
ports:
- 8545:8545
43 changes: 26 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@daostack/arc.js",
"version": "2.0.0-experimental.50",
"version": "2.0.0-experimental.51",
"description": "",
"keywords": [],
"main": "dist/lib/index.js",
Expand Down Expand Up @@ -73,7 +73,7 @@
},
"devDependencies": {
"@daostack/migration-experimental": "0.1.2-rc.6-v2",
"@daostack/test-env-experimental": "4.0.18",
"@daostack/test-env-experimental": "4.0.19",
"@types/graphql": "^14.2.2",
"@types/isomorphic-fetch": "^0.0.34",
"@types/jest": "^24.0.15",
Expand Down
26 changes: 26 additions & 0 deletions src/arc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import BN from 'bn.js'
import { Contract, Signer } from 'ethers'
import { providers } from 'ethers'
import 'ethers/dist/shims'
import { JsonRpcProvider } from 'ethers/providers'
import { BigNumber } from 'ethers/utils'
import gql from 'graphql-tag'
import { Observable, Observer, of } from 'rxjs'
Expand Down Expand Up @@ -627,6 +628,31 @@ export class Arc extends GraphNodeObserver {
Logger.debug(`Data saved successfully as ${descriptionHash}`)
return descriptionHash
}

public approveTokens(tokenAddress: Address, spender: Address, amount: BN) {
const signer = (this.web3 as JsonRpcProvider).getSigner(this.defaultAccount as any)
const abi = [
{
constant: false,
inputs: [
{ name: '_spender', type: 'address' },
{ name: '_value', type: 'uint256' }
],
name: 'approve',
outputs: [{ name: '', type: 'bool' }],
payable: false,
stateMutability: 'nonpayable',
type: 'function'
}
]
const tokenContract = new Contract(tokenAddress, abi, signer)

return this.sendTransaction({
contract: tokenContract,
method: 'approve',
args: [spender, amount.toString()]
})
}
}

export interface IContractInfo {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './plugins/proposal'
export * from './plugins/proposalPlugin'
export * from './plugins/contributionReward'
export * from './plugins/contributionRewardExt'
export * from './plugins/tokenTrade'
export * from './plugins/join'
export * from './plugins/competition'
export * from './plugins/fundingRequest'
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/pluginManager/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export type IProposalCreateOptionsPM =
IProposalCreateOptions<'Join'> |
IProposalCreateOptions<'SchemeRegistrar'> |
IProposalCreateOptions<'SchemeFactory'> |
IProposalCreateOptions<'ReputationFromToken'>
IProposalCreateOptions<'ReputationFromToken'> |
IProposalCreateOptions<'TokenTrade'>

export interface IProposalCreateOptions<TName extends keyof IInitParams>
extends IProposalBaseCreateOptions {
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/tokenTrade/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './plugin'
export * from './proposal'
175 changes: 175 additions & 0 deletions src/plugins/tokenTrade/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import BN from 'bn.js'
import { DocumentNode } from 'graphql'
import gql from 'graphql-tag'
import {
Address,
Arc,
getEventArgs,
IGenesisProtocolParams,
IPluginState,
IProposalBaseCreateOptions,
ITransaction,
ITransactionReceipt,
mapGenesisProtocolParams,
Plugin,
ProposalPlugin,
transactionErrorHandler,
transactionResultHandler
} from '../../index'
import { ITokenTradeProposalState, TokenTradeProposal } from './proposal'

export interface ITokenTradeState extends IPluginState {
pluginParams: {
votingMachine: Address
voteParams: IGenesisProtocolParams
}
}

export interface IProposalCreateOptionsTokenTrade extends IProposalBaseCreateOptions {
sendTokenAddress: Address,
sendTokenAmount: number,
receiveTokenAddress: Address,
receiveTokenAmount: number,
descriptionHash: string
}

export interface IInitParamsTT {
daoId: string
votingMachine: string
votingParams: number[]
voteOnBehalf: string
voteParamsHash: string
}

export class TokenTrade extends ProposalPlugin<
ITokenTradeState,
ITokenTradeProposalState,
IProposalCreateOptionsTokenTrade> {

public static get fragment() {
if (!this.fragmentField) {
this.fragmentField = {
name: 'TokenTradeParams',
fragment: gql` fragment TokenTradeParams on ControllerScheme {
tokenTradeParams {
id
votingMachine
voteParams {
id
queuedVoteRequiredPercentage
queuedVotePeriodLimit
boostedVotePeriodLimit
preBoostedVotePeriodLimit
thresholdConst
limitExponentValue
quietEndingPeriod
proposingRepReward
votersReputationLossRatio
minimumDaoBounty
daoBountyConst
activationTime
voteOnBehalf
}
}
}`
}
}

return this.fragmentField
}

public static initializeParamsMap(initParams: IInitParamsTT) {

Object.keys(initParams).forEach((key) => {
if (initParams[key] === undefined) {
throw new Error(`TokenTrade's initialize parameter '${key}' cannot be undefined`)
}
})

return [
initParams.daoId,
initParams.votingMachine,
initParams.votingParams,
initParams.voteOnBehalf,
initParams.voteParamsHash
]
}

public static itemMap(context: Arc, item: any, queriedId?: string): ITokenTradeState | null {
if (!item) {
return null
}

if (!item.tokenTradeParams) {
throw new Error(`Plugin ${queriedId ? `with id '${queriedId}'` : ''}wrongly instantiated as TokenTrade Plugin`)
}

const baseState = Plugin.itemMapToBaseState(context, item)

const tokenTradeParams = {
voteParams: mapGenesisProtocolParams(item.tokenTradeParams.voteParams),
votingMachine: item.tokenTradeParams.votingMachine
}

return {
...baseState,
pluginParams: tokenTradeParams
}
}

private static fragmentField: { name: string, fragment: DocumentNode } | undefined

public async createProposalTransaction(options: IProposalCreateOptionsTokenTrade): Promise<ITransaction> {

if (options.plugin === undefined) {
throw new Error(`Missing argument "plugin" for TokenTrade in Proposal.create()`)
}
if (!options.receiveTokenAddress) {
throw new Error(`Missing argument "receiveTokenAddress" for TokenTrade in Proposal.create()`)
}
if (!options.sendTokenAddress) {
throw new Error(`Missing argument "sendTokenAddress" for TokenTrade in Proposal.create()`)
}
if (options.receiveTokenAmount <= 0) {
throw new Error(`Argument "receiveTokenAmount" must be greater than 0 for TokenTrade in Proposal.create()`)
}
if (options.sendTokenAmount <= 0) {
throw new Error(`Argument "sendTokenAmount" must be greater than 0 for TokenTrade in Proposal.create()`)
}

if (!options.descriptionHash) {
options.descriptionHash = await this.context.saveIPFSData(options)
}

const { address: pluginAddress } = await this.fetchState()

await this.context.approveTokens(options.sendTokenAddress, pluginAddress, new BN(options.sendTokenAmount)).send()

return {
contract: this.context.getContract(pluginAddress),
method: 'proposeTokenTrade',
args: [
options.sendTokenAddress,
options.sendTokenAmount,
options.receiveTokenAddress,
options.receiveTokenAmount,
options.descriptionHash
]
}
}

public createProposalTransactionMap(): transactionResultHandler<any> {
return async (receipt: ITransactionReceipt) => {
const args = getEventArgs(receipt, 'TokenTradeProposed', 'TokenTrade.createProposal')
const proposalId = args[1]
return new TokenTradeProposal(this.context, proposalId)
}
}

public createProposalErrorHandler(options: IProposalCreateOptionsTokenTrade): transactionErrorHandler {
return async (err) => {
throw err
}
}

}
Loading

0 comments on commit be6b241

Please sign in to comment.