import { providers as ethersProviders , utils as ethersUtils } from 'ethers' ; import { ErrorInvalid } from '../../errors' ; import { TransactionPath } from '../../types' ; import App from '../../entities/App' ; import Transaction from '../../entities/Transaction' ; import { ANY_ENTITY , addressesEqual , includesAddress } from '../address' ; import { findAppMethodFromSignature } from '../app' ; import { encodeCallScript } from '../callScript' ; import { canForward } from '../forwarding' ; import { createDirectTransactionForApp , createForwarderTransactionBuilder , buildForwardingFeePreTransactions , } from '../transactions' ; /**
 * Calculate the forwarding path for a transaction to `destination`
 * that invokes `directTransaction`.
 *
 */ async function calculateForwardingPath ( sender , directTransaction , forwardersWithPermission , forwarders , provider ) { if ( forwardersWithPermission . length === 0 ) { return { path : [ ] , transactions : [ ] } ; } const createForwarderTransaction = createForwarderTransactionBuilder ( sender , directTransaction ) ; const buildForwardingPath = async ( forwarder , script , path , provider ) : Promise < TransactionPath > => { const transaction = createForwarderTransaction ( forwarder , script ) ; try { const forwardingFeePreTransactions = await buildForwardingFeePreTransactions ( transaction , provider ) ; return { transactions : [ ... forwardingFeePreTransactions , transaction ] , path : [ transaction , ... path ] , } ; } catch ( err ) { return { path : [ ] , transactions : [ ] } ; } } ; for ( const forwarder of forwardersWithPermission ) { const script = encodeCallScript ( [ directTransaction ] ) ; if ( await canForward ( forwarder , sender , script , provider ) ) { return buildForwardingPath ( forwarder , script , [ directTransaction ] , provider ) ; } } const filterForwarders = forwarders . filter ( ( forwarder ) => ! includesAddress ( forwardersWithPermission , forwarder ) ) ; const queue = forwardersWithPermission . map ( ( forwarderWithPermission ) => { return [ [ createForwarderTransaction ( forwarderWithPermission , encodeCallScript ( [ directTransaction ] ) ) , directTransaction , ] , filterForwarders , ] ; } ) ; do { const [ path , [ forwarder , ... nextQueue ] ] = queue . shift ( ) ; if ( ! forwarder || path . length > 5 ) continue ; const previousForwarder = path [ 0 ] . to ; const script = encodeCallScript ( [ path [ 0 ] ] ) ; if ( await canForward ( previousForwarder , forwarder , script , provider ) ) { if ( await canForward ( forwarder , sender , script , provider ) ) { return buildForwardingPath ( forwarder , script , path , provider ) ; } else { queue . push ( [ [ createForwarderTransaction ( forwarder , script ) , ... path ] , forwarders . filter ( ( nextForwarder ) => nextForwarder !== forwarder ) , ] ) ; } } queue . push ( [ path , nextQueue ] ) ; } while ( queue . length ) ; return { path : [ ] , transactions : [ ] } ; } /**
 * Calculate the transaction path for a transaction to `destination`
 * that invokes `methodSignature` with `params`.
 *
 */ export async function calculateTransactionPath ( sender , destinationApp , methodSignature , params , apps , provider , finalForwarder ? ) { const directTransaction = await createDirectTransactionForApp ( sender , destinationApp , methodSignature , params ) ; const finalForwarderProvided = finalForwarder ? ethersUtils . isAddress ( finalForwarder ) : false ; const method = findAppMethodFromSignature ( destinationApp , methodSignature ) ; if ( ! method ) { throw new ErrorInvalid ( `No method named ${ methodSignature } on ${ destinationApp . address }` ) ; } if ( ( method ?. roles . length === 0 && ! finalForwarderProvided ) || ( finalForwarder && addressesEqual ( finalForwarder , sender ) ) ) { try { return { path : [ directTransaction ] , transactions : [ directTransaction ] } ; } catch ( _ ) { return { path : [ ] , transactions : [ ] } ; } } const forwarders = apps . filter ( ( app ) => app . isForwarder === true ) . map ( ( app ) => app . address ) ; let forwardersWithPermission = [ ] ; if ( finalForwarderProvided ) { if ( finalForwarder ) { if ( ! includesAddress ( forwarders , finalForwarder ) ) { return { path : [ ] , transactions : [ ] } ; } forwardersWithPermission = [ finalForwarder ] ; } } else { const role = ( await destinationApp . roles ( ) ) . find ( ( role ) => role . name === method . roles [ 0 ] ) ; const allowedEntities = role ?. permissions ?. filter ( ( permission ) => permission . allowed ) . map ( ( permission ) => permission . granteeAddress ) || [ ] ; if ( allowedEntities . length === 0 ) { return { path : [ ] , transactions : [ ] } ; } if ( includesAddress ( allowedEntities , sender ) || includesAddress ( allowedEntities , ANY_ENTITY ) ) { try { return { path : [ directTransaction ] , transactions : [ directTransaction ] } ; } catch ( _ ) { } } forwardersWithPermission = forwarders . filter ( ( forwarder ) => includesAddress ( allowedEntities , forwarder ) ) ; } return calculateForwardingPath ( sender , directTransaction , forwardersWithPermission , forwarders , provider ) ; }