-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit c068bf2
Showing
25 changed files
with
705 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
TENDERLY_ACCOUNT= | ||
TENDERLY_PROJECT= | ||
TENDERLY_API_KEY= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Spell Caster | ||
|
||
Execute a spell on a persisted, forked network with a shareable link. | ||
|
||
## Running | ||
|
||
```bash | ||
bun install # only first time | ||
bun src/index.ts SparkEthereum_20240627 | ||
``` | ||
|
||
## Developing | ||
|
||
```sh | ||
bun fix # to run linter, tests and typecheck | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
{ | ||
"$schema": "https://biomejs.dev/schemas/1.8.1/schema.json", | ||
"files": { | ||
"include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.json", "README.md"], | ||
"ignore": [ | ||
"**/node_modules", | ||
"**/.vscode", | ||
"**/package.json", | ||
"**/dist/**", | ||
"**/.vite", | ||
"packages/app/storybook-static", | ||
"packages/app/__screenshots__", | ||
"packages/app/playwright-report", | ||
"packages/app/test-results", | ||
"src/test/e2e/hars/**" | ||
] | ||
}, | ||
"formatter": { | ||
"enabled": true, | ||
"formatWithErrors": false, | ||
"indentStyle": "space", | ||
"indentWidth": 2, | ||
"lineEnding": "lf", | ||
"lineWidth": 120, | ||
"attributePosition": "auto" | ||
}, | ||
"organizeImports": { "enabled": true }, | ||
"linter": { | ||
"enabled": true, | ||
"rules": { | ||
"recommended": true, | ||
"correctness": { | ||
"useHookAtTopLevel": "error", | ||
"noUnusedImports": "error", | ||
"noUnusedVariables": { | ||
"level": "error", | ||
"fix": "none" | ||
} | ||
}, | ||
"style": { | ||
"useImportType": "off", | ||
"noNonNullAssertion": "off", | ||
"useFilenamingConvention": "off", | ||
"noParameterProperties": "off", | ||
"noParameterAssign": "off" | ||
}, | ||
"suspicious": { | ||
"noSkippedTests": "off", | ||
"noExplicitAny": "off", | ||
"noArrayIndexKey": "off", | ||
"noConsoleLog": "off" | ||
}, | ||
"performance": { | ||
"noAccumulatingSpread": "off" | ||
}, | ||
"complexity": { | ||
"noBannedTypes": "off" | ||
}, | ||
"a11y": { | ||
"all": false | ||
}, | ||
"nursery": { | ||
"useSortedClasses": { | ||
"level": "error", | ||
"options": { | ||
"functions": ["cn", "cva", "clsx"] | ||
} | ||
}, | ||
"noRestrictedImports": { | ||
"level": "error", | ||
"options": { | ||
"paths": { | ||
"node:console": "banned", | ||
"console": "banned" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
"javascript": { | ||
"formatter": { | ||
"jsxQuoteStyle": "double", | ||
"quoteProperties": "asNeeded", | ||
"trailingCommas": "all", | ||
"semicolons": "asNeeded", | ||
"arrowParentheses": "always", | ||
"bracketSpacing": true, | ||
"bracketSameLine": false, | ||
"quoteStyle": "single", | ||
"attributePosition": "auto" | ||
} | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"name": "forker", | ||
"module": "index.ts", | ||
"type": "module", | ||
"scripts": { | ||
"check": "biome check .", | ||
"check:fix": "biome check --write --unsafe .", | ||
"typecheck": "tsc --noEmit", | ||
"test": "bun test", | ||
"fix": "bun run check:fix && bun run test && bun run typecheck" | ||
}, | ||
"devDependencies": { | ||
"@types/bun": "latest" | ||
}, | ||
"peerDependencies": { | ||
"typescript": "^5.0.0" | ||
}, | ||
"dependencies": { | ||
"@biomejs/biome": "^1.8.3", | ||
"fetch-retry": "^6.0.0", | ||
"viem": "^2.16.5" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import assert from 'node:assert' | ||
|
||
export function getRequiredEnv(key: string): string { | ||
const value = process.env[key] | ||
assert(value, `Missing required environment variable: ${key}`) | ||
return value | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import type { Address } from 'viem' | ||
import { gnosis, mainnet } from 'viem/chains' | ||
import { getRequiredEnv } from './env' | ||
|
||
export interface Config { | ||
tenderly: TenderlyConfig | ||
networks: Record<string, NetworkConfig> | ||
} | ||
|
||
export interface NetworkConfig { | ||
name: string | ||
chainId: number | ||
sparkSpellExecutor: Address | ||
} | ||
|
||
export interface TenderlyConfig { | ||
account: string | ||
project: string | ||
apiKey: string | ||
} | ||
|
||
export function getConfig(): Config { | ||
return { | ||
tenderly: { | ||
account: getRequiredEnv('TENDERLY_ACCOUNT'), | ||
project: getRequiredEnv('TENDERLY_PROJECT'), | ||
apiKey: getRequiredEnv('TENDERLY_API_KEY'), | ||
}, | ||
networks: { | ||
[mainnet.id]: { | ||
name: 'mainnet', | ||
chainId: mainnet.id, | ||
sparkSpellExecutor: '0x3300f198988e4C9C63F75dF86De36421f06af8c4', | ||
}, | ||
[gnosis.id]: { | ||
name: 'gnosis', | ||
chainId: gnosis.id, | ||
sparkSpellExecutor: '0xc4218C1127cB24a0D6c1e7D25dc34e10f2625f5A', | ||
}, | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { describe, expect, test } from 'bun:test' | ||
import type { NetworkConfig } from './config' | ||
import { executeSpell } from './executeSpell' | ||
import { getMockEthereumClient } from './test/MockEthereumClient' | ||
import { randomAddress } from './test/addressUtils' | ||
import { asciiToHex, hexStringToHex } from './test/hexUtils' | ||
|
||
describe(executeSpell.name, () => { | ||
test('replaces the code of the executor with a code of a spell', async () => { | ||
const spellAddress = randomAddress('spell') | ||
const network: NetworkConfig = { | ||
name: 'mainnet', | ||
chainId: 1, | ||
sparkSpellExecutor: randomAddress('executor'), | ||
} | ||
const contracts = { [spellAddress]: hexStringToHex(asciiToHex('spell-bytecode')) } | ||
const ethereumClient = getMockEthereumClient(contracts) | ||
|
||
await executeSpell({ spellAddress, network, ethereumClient }) | ||
|
||
expect(ethereumClient.setBytecode).toHaveBeenCalledWith({ | ||
address: network.sparkSpellExecutor, | ||
bytecode: contracts[spellAddress], | ||
}) | ||
}) | ||
|
||
test('restores the code of the executor when done', async () => { | ||
const spellAddress = randomAddress('spell') | ||
const network: NetworkConfig = { | ||
name: 'mainnet', | ||
chainId: 1, | ||
sparkSpellExecutor: randomAddress('executor'), | ||
} | ||
const contracts = { | ||
[spellAddress]: hexStringToHex(asciiToHex('spell-bytecode')), | ||
[network.sparkSpellExecutor]: hexStringToHex(asciiToHex('executor-bytecode')), | ||
} | ||
const ethereumClient = getMockEthereumClient(contracts) | ||
|
||
await executeSpell({ spellAddress, network, ethereumClient }) | ||
|
||
expect(await ethereumClient.getBytecode({ address: network.sparkSpellExecutor })).toBe( | ||
contracts[network.sparkSpellExecutor]!, | ||
) | ||
}) | ||
|
||
test('executes a spell', async () => { | ||
const spellAddress = randomAddress('spell') | ||
const network: NetworkConfig = { | ||
name: 'mainnet', | ||
chainId: 1, | ||
sparkSpellExecutor: randomAddress('executor'), | ||
} | ||
const contracts = { [spellAddress]: hexStringToHex(asciiToHex('spell-bytecode')) } | ||
const ethereumClient = getMockEthereumClient(contracts) | ||
|
||
await executeSpell({ spellAddress, network, ethereumClient }) | ||
|
||
expect(ethereumClient.sendTransaction).toHaveBeenCalledWith({ | ||
to: network.sparkSpellExecutor, | ||
data: expect.stringMatching('0x'), | ||
}) | ||
}) | ||
|
||
test('throws if spell not deployed', async () => { | ||
const spellAddress = randomAddress('spell') | ||
const network: NetworkConfig = { | ||
name: 'mainnet', | ||
chainId: 1, | ||
sparkSpellExecutor: randomAddress('executor'), | ||
} | ||
const contracts = { [spellAddress]: undefined } | ||
const ethereumClient = getMockEthereumClient(contracts) | ||
|
||
expect(async () => await executeSpell({ spellAddress, network, ethereumClient })).toThrowError('Spell not deployed') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import assert from 'node:assert' | ||
import { type Address, encodeFunctionData } from 'viem' | ||
import type { NetworkConfig } from './config' | ||
import type { IEthereumClient } from './periphery/ethereum' | ||
|
||
interface ExecuteSpellArgs { | ||
spellAddress: Address | ||
network: NetworkConfig | ||
ethereumClient: IEthereumClient | ||
} | ||
|
||
export async function executeSpell({ spellAddress, network, ethereumClient }: ExecuteSpellArgs): Promise<void> { | ||
const originalSpellExecutorBytecode = await ethereumClient.getBytecode({ | ||
address: network.sparkSpellExecutor, | ||
}) | ||
|
||
const spellBytecode = await ethereumClient.getBytecode({ | ||
address: spellAddress, | ||
}) | ||
assert(spellBytecode, `Spell not deployed (address=${spellAddress})`) | ||
await ethereumClient.setBytecode({ | ||
address: network.sparkSpellExecutor, | ||
bytecode: spellBytecode, | ||
}) | ||
|
||
await ethereumClient.sendTransaction({ | ||
to: network.sparkSpellExecutor, | ||
data: encodeFunctionData({ | ||
abi: [ | ||
{ | ||
inputs: [], | ||
name: 'execute', | ||
outputs: [], | ||
stateMutability: 'nonpayable', | ||
type: 'function', | ||
}, | ||
], | ||
functionName: 'execute', | ||
}), | ||
}) | ||
|
||
await ethereumClient.setBytecode({ | ||
address: network.sparkSpellExecutor, | ||
bytecode: originalSpellExecutorBytecode, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import assert from 'node:assert' | ||
import { zeroAddress } from 'viem' | ||
import { getConfig } from './config' | ||
import { executeSpell } from './executeSpell' | ||
import { EthereumClient } from './periphery/ethereum' | ||
import { buildAppUrl } from './periphery/spark-app' | ||
import { createTenderlyVNet, getRandomChainId } from './periphery/tenderly' | ||
import { deployContract } from './utils/forge' | ||
import { getChainIdFromSpellName } from './utils/getChainIdFromSpellName' | ||
|
||
const deployer = zeroAddress | ||
|
||
async function main(spellName?: string) { | ||
assert(spellName, 'Pass spell name as an argument ex. SparkEthereum_20240627') | ||
|
||
const config = getConfig() | ||
const originChainId = getChainIdFromSpellName(spellName) | ||
const chain = config.networks[originChainId] | ||
assert(chain, `Chain not found for chainId: ${originChainId}`) | ||
const forkChainId = getRandomChainId() | ||
|
||
console.log(`Executing spell ${spellName} on ${chain.name} (chainId=${originChainId})`) | ||
|
||
const rpc = await createTenderlyVNet({ | ||
account: config.tenderly.account, | ||
apiKey: config.tenderly.apiKey, | ||
project: config.tenderly.project, | ||
originChainId: originChainId, | ||
forkChainId, | ||
}) | ||
const ethereumClient = new EthereumClient(rpc, forkChainId, deployer) | ||
|
||
const spellAddress = await deployContract(spellName, rpc, deployer) | ||
|
||
await executeSpell({ spellAddress, network: chain, ethereumClient }) | ||
|
||
console.log(`Fork Network RPC: ${rpc}`) | ||
console.log(`Spark App URL: ${buildAppUrl({ rpc, originChainId })}`) | ||
} | ||
|
||
const arg1 = process.argv[2] | ||
|
||
await main(arg1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import type { Address, Hex } from 'viem' | ||
import type { IEthereumClient } from '.' | ||
import { getViemClient } from './ViemClient' | ||
|
||
export class EthereumClient implements IEthereumClient { | ||
client: ReturnType<typeof getViemClient> | ||
constructor(rpc: string, forkChainId: number, defaultAccount: Address) { | ||
this.client = getViemClient(rpc, forkChainId, defaultAccount) | ||
} | ||
|
||
async setBytecode(args: { address: Address; bytecode: Hex | undefined }): Promise<void> { | ||
return await this.client.setCode({ | ||
address: args.address, | ||
bytecode: args.bytecode ?? '0x', | ||
}) | ||
} | ||
|
||
async getBytecode(args: { address: Address }): Promise<Hex | undefined> { | ||
const bytecode = await this.client.getCode({ address: args.address }) | ||
if (bytecode === '0x') { | ||
return undefined | ||
} | ||
|
||
return bytecode | ||
} | ||
|
||
async sendTransaction(args: { to: Address; data: Hex }): Promise<void> { | ||
await this.client.sendTransaction({ | ||
to: args.to, | ||
data: args.data, | ||
}) | ||
} | ||
} |
Oops, something went wrong.