-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor: Forwarding Path #227
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { ethers } from 'ethers' | ||
|
||
import App from './App' | ||
import Transaction from './Transaction' | ||
import { | ||
AppOrAddress, | ||
ForwardingPathData, | ||
ForwardingPathDescriptionData, | ||
PostProcessDescription, | ||
} from '../types' | ||
import { describeForwardingPath } from '../utils/description' | ||
|
||
export default class ForwardingPath { | ||
#provider: ethers.providers.Provider | ||
readonly apps: App[] | ||
readonly destination: App | ||
readonly transactions: Transaction[] | ||
|
||
constructor(data: ForwardingPathData, provider: ethers.providers.Provider) { | ||
this.#provider = provider | ||
this.apps = data.apps | ||
this.destination = data.destination | ||
this.transactions = data.transactions | ||
} | ||
|
||
// Lets consumers pass a callback to sign any number of transactions. | ||
// This is similar to calling transactions() and using a loop, but shorter. | ||
// It returns the value returned by the library, usually a transaction receipt. | ||
async sign<Receipt>( | ||
callback: (tx: Transaction) => Promise<Receipt> | ||
): Promise<Receipt[]> { | ||
return Promise.all(this.transactions.map(async (tx) => await callback(tx))) | ||
} | ||
|
||
// Return a description of the forwarding path, to be rendered. | ||
async describe(): Promise<ForwardingPathDescription> { | ||
// TODO: Make sure we are safe to only provide the apps on the path here | ||
return describeForwardingPath(this.transactions, this.apps, this.#provider) | ||
} | ||
|
||
// Return a description of the forwarding path, as text. | ||
// Shorthand for .describe().toString() | ||
toString(): string { | ||
return this.describe().toString() | ||
} | ||
} | ||
|
||
type ForwardingPathDescriptionTreeEntry = | ||
| AppOrAddress | ||
| [AppOrAddress, ForwardingPathDescriptionTreeEntry[]] | ||
|
||
export type ForwardingPathDescriptionTree = ForwardingPathDescriptionTreeEntry[] | ||
|
||
export class ForwardingPathDescription { | ||
readonly apps: App[] | ||
readonly describeSteps: PostProcessDescription[] | ||
|
||
constructor(data: ForwardingPathDescriptionData) { | ||
this.apps = data.apps | ||
this.describeSteps = data.describeSteps | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still not super sure about the data structure we would like to have for the |
||
} | ||
|
||
// Return a tree that can get used to render the path. | ||
tree(): ForwardingPathDescriptionTree { | ||
// TODO: | ||
return [] | ||
} | ||
|
||
// Renders the forwarding path description as text | ||
toString(): string { | ||
return this.tree().toString() | ||
} | ||
|
||
// TBD: a utility that makes it easy to render the tree, | ||
// e.g. as a nested list in HTML or React. | ||
reduce(callback: Function): any {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,9 +6,11 @@ import { | |
} from '@aragon/connect-types' | ||
|
||
import { ConnectionContext } from '../types' | ||
import { decodeForwardingPath } from '../utils/description' | ||
import { toArrayEntry } from '../utils/misc' | ||
import App from './App' | ||
import Intent from './Intent' | ||
import { ForwardingPathDescription } from './ForwardingPath' | ||
import Permission from './Permission' | ||
|
||
// TODO | ||
|
@@ -134,4 +136,10 @@ export default class Organization { | |
this.connection.ethersProvider | ||
) | ||
} | ||
|
||
//////// DESCRIPTIONS ///////// | ||
|
||
async describeScript(script: string): Promise<ForwardingPathDescription> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I decided to expose the |
||
return decodeForwardingPath(script, this.apps(), this.connection) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { ethers } from 'ethers' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is the main logic we are currently using to both encode and decode an evm script and transform it into a |
||
|
||
import { isCallScript, decodeCallScript } from './callScript' | ||
import { isValidForwardCall, parseForwardCall } from './forwarding' | ||
import App from '../entities/App' | ||
import Transaction from '../entities/Transaction' | ||
import { | ||
ForwardingPathDescription, | ||
ForwardingPathDescriptionTree, | ||
} from '../entities/ForwardingPath' | ||
import { | ||
tryEvaluatingRadspec, | ||
postprocessRadspecDescription, | ||
} from './radspec/index' | ||
import { PostProcessDescription } from '../types' | ||
|
||
export async function describeStep( | ||
transaction: Transaction, | ||
apps: App[], | ||
provider: ethers.providers.Provider | ||
): Promise<PostProcessDescription> { | ||
if (!transaction.to) { | ||
throw new Error(`Could not describe transaction: missing 'to'`) | ||
} | ||
if (!transaction.data) { | ||
throw new Error(`Could not describe transaction: missing 'data'`) | ||
} | ||
|
||
let description, annotatedDescription | ||
try { | ||
description = await tryEvaluatingRadspec(transaction, apps, provider) | ||
|
||
if (description) { | ||
const processed = await postprocessRadspecDescription(description, apps) | ||
annotatedDescription = processed.annotatedDescription | ||
description = processed.description | ||
} | ||
} catch (err) { | ||
throw new Error(`Could not describe transaction: ${err}`) | ||
} | ||
|
||
return { | ||
description, | ||
annotatedDescription, | ||
} | ||
} | ||
|
||
/** | ||
* Use radspec to create a human-readable description for each step in the given `path` | ||
* | ||
*/ | ||
export async function describeForwardingPath( | ||
path: Transaction[], | ||
apps: App[], | ||
provider: ethers.providers.Provider | ||
): Promise<ForwardingPathDescription> { | ||
const describeSteps = await Promise.all( | ||
path.map((step) => describeStep(step, apps, provider)) | ||
) | ||
return new ForwardingPathDescription({ describeSteps, apps }) | ||
} | ||
|
||
/** | ||
* Decodes an EVM callscript and returns the forwarding path it description. | ||
* | ||
* @return An array of Ethereum transactions that describe each step in the path | ||
*/ | ||
export function decodeForwardingPath( | ||
script: string | ||
): ForwardingPathDescriptionTree { | ||
// In the future we may support more EVMScripts, but for now let's just assume we're only | ||
// dealing with call scripts | ||
if (!isCallScript(script)) { | ||
throw new Error(`Script could not be decoded: ${script}`) | ||
} | ||
|
||
const path = decodeCallScript(script) | ||
|
||
return path.reduce((decodeSegments, segment) => { | ||
const { data } = segment | ||
|
||
let children | ||
if (isValidForwardCall(data)) { | ||
const forwardedEvmScript = parseForwardCall(data) | ||
|
||
try { | ||
children = decodeForwardingPath(forwardedEvmScript) | ||
// eslint-disable-next-line no-empty | ||
} catch (err) {} | ||
} | ||
|
||
return decodeSegments.concat({ ...segment, children }) | ||
}, [] as ForwardingPathDescriptionTree) | ||
} |
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I decide to use the apps that form the path but I wonder if we might need to know the whole list of installed apps. If that case we need to construct the
ForwardingPath
with a reference to the organization so we can fetch information like the list of apps.