Skip to content
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

Adding tenderly simulation #49

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/consts/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const isDevMode = process?.env?.NODE_ENV === 'development';
const version = process?.env?.NEXT_PUBLIC_VERSION ?? null;
const explorerApiKeys = JSON.parse(process?.env?.EXPLORER_API_KEYS || '{}');
export const TENDERLY_USER=process?.env?.TENDERLY_USER
export const TENDERLY_PROJECT=process?.env?.TENDERLY_PROJECT
export const TENDERLY_ACCESS_KEY=process?.env?.TENDERLY_ACCESS_KEY

interface Config {
debug: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/features/messages/MessageDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export function MessageDetails({ messageId, message: messageFromUrlParams }: Pro
isStatusFetching={isDeliveryStatusFetching}
isPiMsg={message.isPiMsg}
blur={blur}
message={message}
/>
{!message.isPiMsg && <TimelineCard message={message} blur={blur} />}
<ContentDetailsCard message={message} blur={blur} />
Expand Down
73 changes: 67 additions & 6 deletions src/features/messages/cards/TransactionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { BigNumber } from 'bignumber.js';
import { BigNumber as BigNumEth } from 'ethers';
import { PropsWithChildren, ReactNode, useState } from 'react';

import { Spinner } from '../../../components/animations/Spinner';
import { ChainLogo } from '../../../components/icons/ChainLogo';
import { HelpIcon } from '../../../components/icons/HelpIcon';
import { Card } from '../../../components/layout/Card';
import { Modal } from '../../../components/layout/Modal';
import { links } from '../../../consts/links';
import { MessageStatus, MessageTx } from '../../../types';
import { Message, MessageStatus, MessageTx, SimulateBody } from '../../../types';
import { logger } from '../../../utils/logger';
import { getDateTimeString, getHumanReadableTimeString } from '../../../utils/time';
import { getChainDisplayName } from '../../chains/utils';
import { debugStatusToDesc } from '../../debugger/strings';
import { MessageDebugResult } from '../../debugger/types';
import { useMultiProvider } from '../../providers/multiProvider';

import { LabelAndCodeBlock } from './CodeBlock';
import { KeyValueRow } from './KeyValueRow';

Expand Down Expand Up @@ -40,6 +41,7 @@ export function DestinationTransactionCard({
isStatusFetching,
isPiMsg,
blur,
message
}: {
chainId: ChainId;
status: MessageStatus;
Expand All @@ -48,6 +50,7 @@ export function DestinationTransactionCard({
isStatusFetching: boolean;
isPiMsg?: boolean;
blur: boolean;
message:Message;
}) {
let content: ReactNode;
if (transaction) {
Expand All @@ -70,7 +73,7 @@ export function DestinationTransactionCard({
{debugResult.description}
</div>
)}
<CallDataModal debugResult={debugResult} />
<CallDataModal debugResult={debugResult} chainId={chainId} message={message} />
</DeliveryStatus>
);
} else if (status === MessageStatus.Pending) {
Expand All @@ -84,7 +87,7 @@ export function DestinationTransactionCard({
</div>
)}
<Spinner classes="my-4 scale-75" />
<CallDataModal debugResult={debugResult} />
<CallDataModal debugResult={debugResult} chainId={chainId} message={message}/>
</div>
</DeliveryStatus>
);
Expand Down Expand Up @@ -207,10 +210,20 @@ function DeliveryStatus({ children }: PropsWithChildren<unknown>) {
);
}

function CallDataModal({ debugResult }: { debugResult?: MessageDebugResult }) {
function CallDataModal({ debugResult,chainId,message}: { debugResult?: MessageDebugResult,chainId:ChainId,message:Message }) {
const [isOpen, setIsOpen] = useState(false);
const [loading,setLoading]=useState(false)
const [buttonText,setButtonText]=useState("Simulate call with Tenderly")
if (!debugResult?.calldataDetails) return null;
const { contract, handleCalldata } = debugResult.calldataDetails;

const handleClick=async()=>{
setButtonText('Simulating');
setLoading(true)
await simulateCall({contract,handleCalldata,chainId,message})
setButtonText('Simulate call with Tenderly')
setLoading(false) //using !loading is not setting the states properly and the state stays true
}
return (
<>
<button onClick={() => setIsOpen(true)} className={`mt-5 ${styles.textLink}`}>
Expand All @@ -236,11 +249,59 @@ function CallDataModal({ debugResult }: { debugResult?: MessageDebugResult }) {
</p>
<LabelAndCodeBlock label="Recipient contract address:" value={contract} />
<LabelAndCodeBlock label="Handle function input calldata:" value={handleCalldata} />
<button onClick={handleClick}
disabled={loading}
className='underline text-blue-400'
>
{buttonText}
</button>
{loading && <Spinner classes="mt-4 scale-75 self-center" />}
</div>
</Modal>
</>
);
}
async function simulateCall({contract,handleCalldata,chainId,message}:{contract:string,handleCalldata:string,chainId:ChainId,message:Message}){

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap the function contents here in a try/catch and show an error message with toast.error on failure

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't wrap this in try/catch, rather wrapped the backend call which returns failure call and checking if the call is success or failure here.


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spacing? Please run yarn prettier and the files will get auto-formatted :)

const data:SimulateBody={
save: true,
save_if_fails: true,
simulation_type: 'full',
network_id: chainId,
from: '0x0000000000000000000000000000000000000000',//can be any address, doesn't matter
to: contract,
input:handleCalldata,
gas: BigNumEth.from(message.totalGasAmount).toNumber(),
gas_price: Number(computeAvgGasPrice("wei",message.totalGasAmount,message.totalPayment)),
value: 0,
}
const resp=await fetch(
`/api/simulation`,{
method:'POST',
body:JSON.stringify(data),
}
)

const simulationId=await resp.json().then((data)=>data.data)
window.open(`https://dashboard.tenderly.co/shared/simulation/${simulationId}`)

}

function computeAvgGasPrice(unit: string, gasAmount?: BigNumber.Value, payment?: BigNumber.Value) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you duplicate this function here? Please de-dupe with the copy in GasDetailsCard and move it to messages/utils.ts

try {
if (!gasAmount || !payment) return null;
const gasBN = new BigNumber(gasAmount);
const paymentBN = new BigNumber(payment);
if (gasBN.isZero() || paymentBN.isZero()) return null;
const wei = paymentBN.div(gasAmount).toFixed(0);
return wei;
} catch (error) {
logger.debug('Error computing avg gas price', error);
return null;
}
}


const helpText = {
origin: 'Info about the transaction that initiated the message placement into the outbox.',
Expand Down
31 changes: 31 additions & 0 deletions src/pages/api/simulation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { TENDERLY_ACCESS_KEY, TENDERLY_PROJECT, TENDERLY_USER } from "../../consts/config"
import { successResult } from "../../features/api/utils"
export default async function handler(req,res){
const data=req.body

if(!TENDERLY_ACCESS_KEY || !TENDERLY_PROJECT || !TENDERLY_USER){
console.log("ENV not defined")
return null
}
const resp = await fetch(
`https://api.tenderly.co/api/v1/account/${TENDERLY_USER}/project/${TENDERLY_PROJECT}/simulate`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check that these env vars are defined and throw a helpful here at the top if they're not

{
method:'POST',
body:data,
headers: {
'X-Access-Key': TENDERLY_ACCESS_KEY as string,
},
}
);
const simulationId=await resp.json().then((data)=>data.simulation.id)
await fetch(
`https://api.tenderly.co/api/v1/account/${TENDERLY_USER}/project/${TENDERLY_PROJECT}/simulations/${simulationId}/share`,
{
method:'POST',
headers: {
'X-Access-Key': TENDERLY_ACCESS_KEY as string,
},
}
)
res.json(successResult(simulationId))
}
13 changes: 13 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,16 @@ export interface ExtendedLog extends providers.Log {
from?: Address;
to?: Address;
}

export interface SimulateBody {
save:boolean;
save_if_fails:boolean;
simulation_type:string,
network_id:ChainId,
from:Address,//can be any address, doesn't matter
to:Address,
input:string,
gas:number,
gas_price:number|null,
value:number,
}
Loading