1
1
import { command } from "cmd-ts" ;
2
2
import * as allArgs from '../../args' ;
3
- import { TState , requires , loggedIn , isLoggedIn , TLoggedInState , inRepo } from "../../inject" ;
3
+ import { TState , requires , isLoggedIn , inRepo , TInRepoState , isInRepo } from "../../inject" ;
4
4
import { configs , getRepoRoot } from '../../configs' ;
5
5
import { getActiveDeploy , phaseType , formatNow , blankDeploy } from "./utils" ;
6
6
import { join , normalize } from 'path' ;
7
7
import { existsSync , lstatSync } from "fs" ;
8
- import { HaltDeployError , PauseDeployError , TExecuteOptions } from "../../../signing/strategy" ;
8
+ import { HaltDeployError , PauseDeployError , TStrategyOptions } from "../../../signing/strategy" ;
9
9
import chalk from "chalk" ;
10
10
import { canonicalPaths } from "../../../metadata/paths" ;
11
- import { createTestClient , http , TestClient , toHex } from "viem" ;
11
+ import { createTestClient , http , parseEther , toHex } from "viem" ;
12
12
import * as AllChains from "viem/chains" ;
13
13
import * as allChains from 'viem/chains' ;
14
14
import semver from 'semver' ;
15
15
import { SavebleDocument , Transaction } from "../../../metadata/metadataStore" ;
16
16
import { chainIdName } from "../../prompts" ;
17
17
import { AnvilOptions , AnvilService } from '@foundry-rs/hardhat-anvil/dist/src/anvil-service' ;
18
- import { mnemonicToAccount } from 'viem/accounts'
19
- import { TenderlyVirtualTestnetClient } from "./utils-tenderly" ;
18
+ import { generatePrivateKey , mnemonicToAccount , privateKeyToAccount } from 'viem/accounts'
19
+ import { TenderlyVirtualTestnetClient , VirtualTestNetResponse } from "./utils-tenderly" ;
20
+ import { AnvilTestClient , TenderlyTestClient , TestClient } from "./utils-testnets" ;
20
21
import { TDeploy , TEnvironmentManifest , TUpgrade } from "../../../metadata/schema" ;
21
22
import { acquireDeployLock , releaseDeployLock } from "./utils-locks" ;
22
23
import { executeSystemPhase } from "../../../deploy/handlers/system" ;
@@ -36,7 +37,7 @@ const DEFAULT_ANVIL_URI = `http://127.0.0.1:8546/`;
36
37
37
38
const isValidFork = ( fork : string | undefined ) => {
38
39
// TODO(tenderly) - support tenderly.
39
- return [ undefined , `anvil` ] . includes ( fork ) ;
40
+ return [ undefined , `anvil` , `tenderly` ] . includes ( fork ) ;
40
41
}
41
42
42
43
@@ -48,22 +49,26 @@ const throwIfUnset = (envVar: string | undefined, msg: string) => {
48
49
}
49
50
50
51
export async function handler ( _user : TState , args : { env : string , resume : boolean , rpcUrl : string | undefined , json : boolean , upgrade : string | undefined , nonInteractive : boolean | undefined , fork : string | undefined } ) {
51
- if ( ! isLoggedIn ( _user ) ) {
52
- return ;
53
- }
54
-
55
52
if ( ! isValidFork ( args . fork ) ) {
56
53
throw new Error ( `Invalid value for 'fork' - expected one of (tenderly, anvil)` ) ;
57
54
}
58
55
59
- const user : TLoggedInState = _user ;
56
+ if ( ! args . fork && ! isLoggedIn ( _user ) ) {
57
+ throw new Error ( `To deploy to an environment, you must run from within the contracts repo while logged in.` ) ;
58
+ } else if ( ! isInRepo ( _user ) ) {
59
+ throw new Error ( `Must be run from within the contracts repo.` ) ;
60
+ }
61
+ const user = _user ;
62
+
60
63
const repoConfig = await configs . zeus . load ( ) ;
61
64
if ( ! repoConfig ) {
62
65
console . error ( "This repo is not setup. Try `zeus init` first." ) ;
63
66
return ;
64
67
}
65
68
66
69
let anvil : AnvilService | undefined ;
70
+ let tenderly : TenderlyVirtualTestnetClient | undefined ;
71
+ let tenderlyTestnet : VirtualTestNetResponse | undefined ;
67
72
let overrideEoaPk : `0x${string } ` | undefined ;
68
73
let overrideRpcUrl : string | undefined ;
69
74
let testClient : TestClient | undefined ;
@@ -79,33 +84,52 @@ export async function handler(_user: TState, args: {env: string, resume: boolean
79
84
80
85
switch ( args . fork ) {
81
86
case `tenderly` : {
82
- const tenderly = new TenderlyVirtualTestnetClient (
87
+ tenderly = new TenderlyVirtualTestnetClient (
83
88
throwIfUnset ( process . env . TENDERLY_API_KEY , "Expected TENDERLY_API_KEY to be set." ) ,
84
89
throwIfUnset ( process . env . TENDERLY_ACCOUNT_SLUG , "Expected TENDERLY_ACCOUNT_SLUG to be set." ) ,
85
90
throwIfUnset ( process . env . TENDERLY_PROJECT_SLUG , "Expected TENDERLY_PROJECT_SLUG to be set." ) ,
86
91
) ;
87
92
88
- // TODO(tenderly): continue from here
89
- const _vnetId = await tenderly . createVirtualNetwork ( {
90
- slug : `z- ${ args . env } - ${ formatNow ( ) } ` ,
91
- display_name : `zeus testnet ` ,
92
- description : `CLI-created zeus testnet ` ,
93
+ const vnetSlug = `z- ${ args . env } - ${ args . upgrade } - ${ formatNow ( ) } `
94
+ tenderlyTestnet = await tenderly . createVirtualNetwork ( {
95
+ slug : vnetSlug ,
96
+ display_name : `${ args . env } - ${ args . upgrade } ` ,
97
+ description : `automatically deployed via Zeus ` ,
93
98
fork_config : {
94
- network_id : envManifest . _ . chainId . toString ( ) ,
99
+ network_id : Number ( envManifest . _ . chainId ) ,
95
100
} ,
96
101
virtual_network_config : {
97
102
chain_config : {
98
- chain_id : envManifest . _ . chainId
103
+ chain_id : Number ( envManifest . _ . chainId )
99
104
} ,
100
105
sync_state_config : {
101
106
enabled : false
102
107
} ,
103
108
explorer_page_config : {
104
109
enabled : false
105
- }
110
+ } ,
106
111
}
107
112
} )
113
+ const tenderlyRpcUrl = tenderlyTestnet . rpcs ? tenderlyTestnet . rpcs [ 0 ] . url : undefined ;
114
+ if ( ! tenderlyRpcUrl ) {
115
+ throw new Error ( `Failed to load tenderly RPC URL.` ) ;
116
+ }
108
117
118
+ console . log ( chalk . green ( `+ created tenderly devnet: https://dashboard.tenderly.co/${ process . env . TENDERLY_ACCOUNT_SLUG } /${ process . env . TENDERLY_PROJECT_SLUG } /testnet/${ tenderlyTestnet . id } \n` ) ) ;
119
+
120
+ testClient = new TenderlyTestClient ( tenderlyRpcUrl ) ;
121
+
122
+ const specialAccountPk = generatePrivateKey ( ) ;
123
+ const specialAccountAddress = privateKeyToAccount ( specialAccountPk ) . address ;
124
+
125
+ overrideRpcUrl = tenderlyRpcUrl ;
126
+ overrideEoaPk = specialAccountPk ;
127
+
128
+ // fund the special account.
129
+ console . log ( chalk . gray ( `+ using deployer address ${ specialAccountAddress } ` ) ) ;
130
+
131
+ await testClient . setBalance ( specialAccountAddress , parseEther ( '420' ) ) ;
132
+
109
133
break ;
110
134
}
111
135
case `anvil` : {
@@ -123,10 +147,11 @@ export async function handler(_user: TState, args: {env: string, resume: boolean
123
147
}
124
148
} ;
125
149
anvil = await AnvilService . create ( opts , false ) ;
126
- testClient = createTestClient ( {
150
+ const viemClient = createTestClient ( {
127
151
mode : 'anvil' ,
128
152
transport : http ( DEFAULT_ANVIL_URI )
129
153
} ) ;
154
+ testClient = new AnvilTestClient ( viemClient ) ;
130
155
const pkRaw = mnemonicToAccount ( ANVIL_MNENOMIC , { accountIndex : 0 } ) . getHdKey ( ) . privateKey ;
131
156
if ( ! pkRaw ) {
132
157
throw new Error ( `Invalid private key for anvil test account.` ) ;
@@ -151,12 +176,16 @@ export async function handler(_user: TState, args: {env: string, resume: boolean
151
176
152
177
console . log ( `[${ chainIdName ( deploy . _ . chainId ) } ] Resuming existing deploy... (began at ${ deploy . _ . startTime } )` ) ;
153
178
return await executeOrContinueDeployWithLock ( deploy . _ . name , deploy . _ . env , user , {
154
- rpcUrl : overrideRpcUrl ,
155
- nonInteractive : ! ! args . nonInteractive ,
156
- fork : args . fork ,
157
- anvil,
158
- testClient,
159
- overrideEoaPk,
179
+ defaultArgs : {
180
+ rpcUrl : overrideRpcUrl ,
181
+ nonInteractive : ! ! args . nonInteractive ,
182
+ fork : args . fork ,
183
+ anvil,
184
+ testClient,
185
+ overrideEoaPk,
186
+ etherscanApiKey : ! args . fork ,
187
+ } ,
188
+ nonInteractive : ! ! args . fork
160
189
} ) ;
161
190
} else if ( args . resume ) {
162
191
console . error ( `Nothing to resume.` ) ;
@@ -196,12 +225,16 @@ export async function handler(_user: TState, args: {env: string, resume: boolean
196
225
console . log ( chalk . green ( `+ started deploy (${ envManifest ?. _ . deployedVersion ?? '0.0.0' } ) => (${ upgradeManifest . _ . to } ) (requires: ${ upgradeManifest . _ . from } )` ) ) ;
197
226
await metaTxn . commit ( `started deploy: ${ deployJson . _ . env } /${ deployJson . _ . name } ` ) ;
198
227
await executeOrContinueDeployWithLock ( deployJson . _ . name , deployJson . _ . env , user , {
199
- rpcUrl : overrideRpcUrl ?? args . rpcUrl ,
200
- nonInteractive : ! ! args . nonInteractive ,
201
- fork : args . fork ,
202
- anvil,
203
- testClient,
204
- overrideEoaPk,
228
+ defaultArgs : {
229
+ rpcUrl : overrideRpcUrl ?? args . rpcUrl ,
230
+ nonInteractive : ! ! args . nonInteractive ,
231
+ fork : args . fork ,
232
+ anvil,
233
+ testClient,
234
+ overrideEoaPk,
235
+ etherscanApiKey : ! args . fork ,
236
+ } ,
237
+ nonInteractive : ! ! args . fork
205
238
} ) ;
206
239
} finally {
207
240
// shut down anvil after running
@@ -210,12 +243,15 @@ export async function handler(_user: TState, args: {env: string, resume: boolean
210
243
}
211
244
}
212
245
213
- const executeOrContinueDeployWithLock = async ( name : string , env : string , user : TLoggedInState , options : TExecuteOptions ) => {
214
- const shouldUseLock = ! options . fork ;
215
- if ( options . fork ) {
246
+ const executeOrContinueDeployWithLock = async ( name : string , env : string , user : TInRepoState , options : TStrategyOptions ) => {
247
+ const shouldUseLock = ! options . defaultArgs . fork ;
248
+ if ( options . defaultArgs . fork ) {
216
249
// explicitly choose the logged out metadata store, to avoid writing anything.
250
+ chalk . bold ( `Deploying to fork: ${ options . defaultArgs . fork } ` ) ;
217
251
user . metadataStore = user . loggedOutMetadataStore ;
218
252
options . nonInteractive = true ;
253
+ options . defaultArgs . nonInteractive = true ;
254
+ options . defaultArgs . etherscanApiKey = false ;
219
255
}
220
256
221
257
const txn = await user . metadataStore . begin ( ) ;
@@ -249,7 +285,7 @@ const executeOrContinueDeployWithLock = async (name: string, env: string, user:
249
285
}
250
286
}
251
287
252
- const executeOrContinueDeploy = async ( deploy : SavebleDocument < TDeploy > , _user : TState , metatxn : Transaction , options : TExecuteOptions ) => {
288
+ const executeOrContinueDeploy = async ( deploy : SavebleDocument < TDeploy > , _user : TState , metatxn : Transaction , options : TStrategyOptions ) => {
253
289
try {
254
290
while ( true ) {
255
291
console . log ( chalk . green ( `[${ deploy . _ . segments [ deploy . _ . segmentId ] ?. filename ?? '<none>' } ] ${ deploy . _ . phase } ` ) )
@@ -266,7 +302,7 @@ const executeOrContinueDeploy = async (deploy: SavebleDocument<TDeploy>, _user:
266
302
case 'eoa' : {
267
303
await executeEOAPhase ( deploy , metatxn , options )
268
304
break ;
269
- }
305
+ }
270
306
case 'script' : {
271
307
await executeScriptPhase ( deploy , metatxn , options ) ;
272
308
break ;
@@ -317,5 +353,5 @@ export default command({
317
353
nonInteractive : allArgs . nonInteractive ,
318
354
fork : allArgs . fork
319
355
} ,
320
- handler : requires ( handler , loggedIn , inRepo ) ,
356
+ handler : requires ( handler , inRepo ) ,
321
357
} )
0 commit comments