diff --git a/.gitignore b/.gitignore index be7bb84..c6ff3bb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ cache/* out/ .notes.md ks-0x12240F5668f61fd4dF728584206b21d70dD87232 -ks-0xA84E4F9D72cb37A8276090D3FC50895BD8E5Aaf1 \ No newline at end of file +ks-0xA84E4F9D72cb37A8276090D3FC50895BD8E5Aaf1 +.hack.md \ No newline at end of file diff --git a/src/data/contracts/registry.rs b/src/data/contracts/registry.rs index 08206ea..1d004cd 100644 --- a/src/data/contracts/registry.rs +++ b/src/data/contracts/registry.rs @@ -77,6 +77,18 @@ pub const CONTRACTS: &[ContractSpec] = &[ )), verify_json_path: None, }, + ContractSpec { + name: "ApproveAndSwapKyberSwapHook", + address: Some(address!("0xdC9D10d9710DBf82924a3F7733293457Ad12D37D")), + salt: None, + path: None, + deployer_tx: Some(&hex!( + "4cddabff926dd66bb6638bcbe02b70423487b9fb5504ba145189f0d7aa4075c5610100604052348015610010575f5ffd5b5060405161216f38038061216f83398101604081905261002f916100d7565b6040805180820190915260048152630537761760e41b6020909101525f805460ff191690557fbce316f0d9d2a3affa97de1d99bb2aac0538e2666d0d8545545ead241ef0ccab6080526001600160a01b03831661009f57604051630f58648f60e01b815260040160405180910390fd5b6001600160a01b0392831660a05290821660c0521660e052610117565b80516001600160a01b03811681146100d2575f5ffd5b919050565b5f5f5f606084860312156100e9575f5ffd5b6100f2846100bc565b9250610100602085016100bc565b915061010e604085016100bc565b90509250925092565b60805160a05160c05160e05161200261016d5f395f81816102b7015261108101525f81816102de0152610bc201525f818161022401528181610d5501526111f601525f81816101f3015261029001526120025ff3fe608060405234801561000f575f5ffd5b5060043610610127575f3560e01c80636a014517116100a9578063a0cf0aea1161006e578063a0cf0aea146102b2578063c9b4f6da146102d9578063d06a1e8914610300578063e445e7dd14610313578063e77455171461032c575f5ffd5b80636a0145171461021f57806381cbe691146102465780638c4313c1146102595780638e14877614610279578063984d6e7a1461028b575f5ffd5b80632ae2fe3d116100ef5780632ae2fe3d146101ac57806338d52e0f146101bf5780633b5896bc146101d15780635492e677146101f1578063685a943c14610217575f5ffd5b806301bd43ef1461012b57806305b4fe911461014757806314cb37cf1461015c5780631f2646191461016f5780632113522a14610182575b5f5ffd5b61013460035c81565b6040519081526020015b60405180910390f35b61015a61015536600461163f565b61034f565b005b61015a61016a36600461169f565b6103c9565b61015a61017d3660046116ba565b6103ea565b6101946001600160a01b0360045c1681565b6040516001600160a01b03909116815260200161013e565b61015a6101ba36600461163f565b610443565b6101946001600160a01b0360025c1681565b6101e46101df36600461163f565b6104b6565b60405161013e9190611716565b7f0000000000000000000000000000000000000000000000000000000000000000610134565b6101345f5c81565b6101947f000000000000000000000000000000000000000000000000000000000000000081565b61013461025436600461169f565b6106ce565b61026c6102673660046117a3565b6106e6565b60405161013e91906117e1565b6101946001600160a01b0360015c1681565b6101347f000000000000000000000000000000000000000000000000000000000000000081565b6101947f000000000000000000000000000000000000000000000000000000000000000081565b6101947f000000000000000000000000000000000000000000000000000000000000000081565b61015a61030e36600461169f565b6107eb565b5f5461031f9060ff1681565b60405161013e91906117f3565b61033f61033a3660046118ce565b610868565b604051901515815260200161013e565b336001600160a01b038416146103785760405163e15e56c960e01b815260040160405180910390fd5b5f61038284610874565b905061038d81610887565b156103ab5760405163945b63f560e01b815260040160405180910390fd5b6103b6816001610894565b6103c2858585856108aa565b5050505050565b6103d28161090b565b50336004805c6001600160a01b0319168217905d5050565b5f6103f482610874565b90506103ff8161093d565b8061040e575061040e81610887565b1561042c5760405163441e4c7360e11b815260040160405180910390fd5b5f610438826001610946565b905083815d50505050565b336001600160a01b0384161461046c5760405163e15e56c960e01b815260040160405180910390fd5b5f61047684610874565b90506104818161093d565b1561049f57604051630bbb04d960e11b815260040160405180910390fd5b6104aa81600161099b565b6103c2858585856109a7565b60605f6104c5868686866109e9565b9050805160026104d5919061195b565b6001600160401b038111156104ec576104ec611819565b60405190808252806020026020018201604052801561053857816020015b60408051606080820183525f80835260208301529181019190915281526020019060019003908161050a5790505b5091506040518060600160405280306001600160a01b031681526020015f8152602001306001600160a01b0316632ae2fe3d89898989604051602401610581949392919061196e565b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050815250825f815181106105c3576105c36119b6565b60209081029190910101525f5b8151811015610624578181815181106105eb576105eb6119b6565b602002602001015183826001610601919061195b565b81518110610611576106116119b6565b60209081029190910101526001016105d0565b506040518060600160405280306001600160a01b031681526020015f8152602001306001600160a01b03166305b4fe918989898960405160240161066b949392919061196e565b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505081525082600184516106aa91906119ca565b815181106106ba576106ba6119b6565b602002602001018190525050949350505050565b5f6106e06106db83610874565b610e4b565b92915050565b60605f61072a84848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060699250610e58915050565b90505f61077085858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060899250869150610eba9050565b90505f61078b82600480855161078691906119ca565b610eba565b80602001905181019061079e9190611c67565b90508060600151602001516040516020016107d1919060609190911b6bffffffffffffffffffffffff1916815260140190565b604051602081830303815290604052935050505092915050565b336001600160a01b0360045c16146108165760405163e15e56c960e01b815260040160405180910390fd5b5f61082082610874565b905061082b8161093d565b158061083d575061083b81610887565b155b1561085b57604051634bd439b560e11b815260040160405180910390fd5b61086481610fc1565b5050565b5f6106e0826068610fd8565b5f5f61087f83611004565b5c9392505050565b5f5f61087f836003610946565b5f6108a0836003610946565b905081815d505050565b6109056108b6846106ce565b6108f58585858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061107092505050565b6108ff91906119ca565b8461113a565b50505050565b5f6003805c908261091b83611d47565b9190505d505f61092a83611004565b905060035c80825d505060035c92915050565b5f5f61087f8360025b604080517fc41a07c57a776f6217c6d4278dfe7a5d114742afe55627ce458c2802aa4d703460208083019190915281830194909452606080820193909352815180820390930183526080019052805191012090565b5f6108a0836002610946565b6109056108ff8484848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061107092505050565b60605f610a2a84848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201829052509250611152915050565b90505f610a6e85858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060289250610e58915050565b90505f610ab286868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060689250610fd8915050565b90505f610af687878080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060699250610e58915050565b90505f610b3c88888080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525060899250869150610eba9050565b90505f610b48826111b6565b90508315610bef576040516381cbe69160e01b81526001600160a01b038b811660048301525f91908d16906381cbe69190602401602060405180830381865afa158015610b97573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bbb9190611d5f565b9050610be97f0000000000000000000000000000000000000000000000000000000000000000848389611225565b90955091505b60408051600480825260a0820190925290816020015b60408051606080820183525f808352602083015291810191909152815260200190600190039081610c0557905050604080516060810182526001600160a01b03808a1682525f60208301819052835191861660248301526044820152929950919082019060640160408051601f198184030181529190526020810180516001600160e01b031663095ea7b360e01b1790529052875188905f90610caa57610caa6119b6565b60200260200101819052506040518060600160405280876001600160a01b031681526020015f81526020018287604051602401610cfc9291906001600160a01b03929092168252602082015260400190565b60408051601f198184030181529190526020810180516001600160e01b031663095ea7b360e01b1790529052875188906001908110610d3d57610d3d6119b6565b602002602001018190525060405180606001604052807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031681526020015f81526020018381525087600281518110610d9f57610d9f6119b6565b60200260200101819052506040518060600160405280876001600160a01b031681526020015f8152602001825f604051602401610df19291906001600160a01b03929092168252602082015260400190565b60408051601f198184030181529190526020810180516001600160e01b031663095ea7b360e01b1790529052875188906003908110610e3257610e326119b6565b6020026020010181905250505050505050949350505050565b5f5f61087f836001610946565b5f610e6482602061195b565b83511015610eb15760405162461bcd60e51b8152602060048201526015602482015274746f55696e743235365f6f75744f66426f756e647360581b60448201526064015b60405180910390fd5b50016020015190565b60608182601f011015610f005760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b6044820152606401610ea8565b610f0a828461195b565b84511015610f4e5760405162461bcd60e51b8152602060048201526011602482015270736c6963655f6f75744f66426f756e647360781b6044820152606401610ea8565b606082158015610f6c5760405191505f825260208201604052610fb6565b6040519150601f8416801560200281840101858101878315602002848b0101015b81831015610fa5578051835260209283019201610f8d565b5050858452601f01601f1916604052505b5090505b9392505050565b610fcb815f61099b565b610fd5815f610894565b50565b5f828281518110610feb57610feb6119b6565b01602001516001600160f81b0319161515905092915050565b5f7f8ce092d4869bdfa1a1ce579ddddb2b776c39ace5d7b5d7e70e97ece2a78f4f9b8260405160200161105392919091825260601b6bffffffffffffffffffffffff1916602082015260340190565b604051602081830303815290604052805190602001209050919050565b5f5f61107d836014611152565b90507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001600160a01b0316036110ca5750506001600160a01b038216316106e0565b6040516370a0823160e01b81526001600160a01b0385811660048301528216906370a0823190602401602060405180830381865afa15801561110e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111329190611d5f565b949350505050565b5f61114482610874565b90505f610438826001610946565b5f61115e82601461195b565b835110156111a65760405162461bcd60e51b8152602060048201526015602482015274746f416464726573735f6f75744f66426f756e647360581b6044820152606401610ea8565b500160200151600160601b900490565b5f5f6111cb83600480865161078691906119ca565b8060200190518101906111de9190611c67565b60208101519091506001600160a01b031661121b57507f000000000000000000000000000000000000000000000000000000000000000092915050565b6020015192915050565b6060825f0361124757604051630f6fa54560e41b815260040160405180910390fd5b6001600160a01b038516156112d75760405163726d379760e11b81526001600160a01b0386169063e4da6f2e906112849087908790600401611d76565b5f60405180830381865afa9250505080156112c057506040513d5f823e601f3d908101601f191682016040526112bd9190810190611d97565b60015b156112d75781156112d45791506111329050565b50505b6112e28484846112eb565b95945050505050565b6060815f0361130d57604051630f6fa54560e41b815260040160405180910390fd5b5f61132185600480885161078691906119ca565b8060200190518101906113349190611c67565b905061134a84848360600151610100015161147b565b60608201805161010001919091525160e0018490525f5b816060015160600151518110156113cb5761139e826060015160600151828151811061138f5761138f6119b6565b6020026020010151868661150a565b82606001516060015182815181106113b8576113b86119b6565b6020908102919091010152600101611361565b505f5b816060015160a0015151811015611429576113fc826060015160a00151828151811061138f5761138f6119b6565b826060015160a001518281518110611416576114166119b6565b60209081029190910101526001016113ce565b5060405163e21fd0e960e01b90611444908390602001611e5b565b60408051601f19818403018152908290526114629291602001611fb6565b6040516020818303038152906040529150509392505050565b5f825f0361148a575080610fba565b82841461150357828411156114d3575f6114b16114a785876119ca565b620186a08661150a565b90506114c18382620186a061150a565b6114cb908461195b565b925050611503565b5f6114e16114a786866119ca565b90505f6114f28483620186a061150a565b90506114fe81856119ca565b935050505b5092915050565b5f5f5f61151786866115ba565b91509150815f0361153b5783818161153157611531611fe1565b0492505050610fba565b8184116115525761155260038515026011186115d6565b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010185841190960395909502919093039390930492909217029150509392505050565b5f805f1983850993909202808410938190039390930393915050565b634e487b715f52806020526024601cfd5b6001600160a01b0381168114610fd5575f5ffd5b5f5f83601f84011261160b575f5ffd5b5081356001600160401b03811115611621575f5ffd5b602083019150836020828501011115611638575f5ffd5b9250929050565b5f5f5f5f60608587031215611652575f5ffd5b843561165d816115e7565b9350602085013561166d816115e7565b925060408501356001600160401b03811115611687575f5ffd5b611693878288016115fb565b95989497509550505050565b5f602082840312156116af575f5ffd5b8135610fba816115e7565b5f5f604083850312156116cb575f5ffd5b8235915060208301356116dd816115e7565b809150509250929050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b8281101561179757868503603f19018452815180516001600160a01b0316865260208082015190870152604090810151606091870182905290611781908701826116e8565b955050602093840193919091019060010161173c565b50929695505050505050565b5f5f602083850312156117b4575f5ffd5b82356001600160401b038111156117c9575f5ffd5b6117d5858286016115fb565b90969095509350505050565b602081525f610fba60208301846116e8565b602081016003831061181357634e487b7160e01b5f52602160045260245ffd5b91905290565b634e487b7160e01b5f52604160045260245ffd5b60405161016081016001600160401b038111828210171561185057611850611819565b60405290565b60405160a081016001600160401b038111828210171561185057611850611819565b604051601f8201601f191681016001600160401b03811182821017156118a0576118a0611819565b604052919050565b5f6001600160401b038211156118c0576118c0611819565b50601f01601f191660200190565b5f602082840312156118de575f5ffd5b81356001600160401b038111156118f3575f5ffd5b8201601f81018413611903575f5ffd5b8035611916611911826118a8565b611878565b81815285602083850101111561192a575f5ffd5b816020840160208301375f91810160200191909152949350505050565b634e487b7160e01b5f52601160045260245ffd5b808201808211156106e0576106e0611947565b6001600160a01b038581168252841660208201526060604082018190528101829052818360808301375f818301608090810191909152601f909201601f191601019392505050565b634e487b7160e01b5f52603260045260245ffd5b818103818111156106e0576106e0611947565b80516119e8816115e7565b919050565b5f82601f8301126119fc575f5ffd5b8151611a0a611911826118a8565b818152846020838601011115611a1e575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b5f6001600160401b03821115611a5257611a52611819565b5060051b60200190565b5f82601f830112611a6b575f5ffd5b8151611a7961191182611a3a565b8082825260208201915060208360051b860101925085831115611a9a575f5ffd5b602085015b83811015611ac0578051611ab2816115e7565b835260209283019201611a9f565b5095945050505050565b5f82601f830112611ad9575f5ffd5b8151611ae761191182611a3a565b8082825260208201915060208360051b860101925085831115611b08575f5ffd5b602085015b83811015611ac0578051835260209283019201611b0d565b5f6101608284031215611b36575f5ffd5b611b3e61182d565b9050611b49826119dd565b8152611b57602083016119dd565b602082015260408201516001600160401b03811115611b74575f5ffd5b611b8084828501611a5c565b60408301525060608201516001600160401b03811115611b9e575f5ffd5b611baa84828501611aca565b60608301525060808201516001600160401b03811115611bc8575f5ffd5b611bd484828501611a5c565b60808301525060a08201516001600160401b03811115611bf2575f5ffd5b611bfe84828501611aca565b60a083015250611c1060c083016119dd565b60c082015260e08281015190820152610100808301519082015261012080830151908201526101408201516001600160401b03811115611c4e575f5ffd5b611c5a848285016119ed565b6101408301525092915050565b5f60208284031215611c77575f5ffd5b81516001600160401b03811115611c8c575f5ffd5b820160a08185031215611c9d575f5ffd5b611ca5611856565b611cae826119dd565b8152611cbc602083016119dd565b602082015260408201516001600160401b03811115611cd9575f5ffd5b611ce5868285016119ed565b60408301525060608201516001600160401b03811115611d03575f5ffd5b611d0f86828501611b25565b60608301525060808201516001600160401b03811115611d2d575f5ffd5b611d39868285016119ed565b608083015250949350505050565b5f60018201611d5857611d58611947565b5060010190565b5f60208284031215611d6f575f5ffd5b5051919050565b604081525f611d8860408301856116e8565b90508260208301529392505050565b5f5f60408385031215611da8575f5ffd5b82518015158114611db7575f5ffd5b60208401519092506001600160401b03811115611dd2575f5ffd5b611dde858286016119ed565b9150509250929050565b5f8151808452602084019350602083015f5b82811015611e215781516001600160a01b0316865260209586019590910190600101611dfa565b5093949350505050565b5f8151808452602084019350602083015f5b82811015611e21578151865260209586019590910190600101611e3d565b602080825282516001600160a01b0390811683830152908301511660408083019190915282015160a060608301525f90611e9860c08401826116e8565b6060850151848203601f1901608086015280516001600160a01b031682529091506020810151611ed360208401826001600160a01b03169052565b5060408101516101606040840152611eef610160840182611de8565b905060608201518382036060850152611f088282611e2b565b91505060808201518382036080850152611f228282611de8565b91505060a082015183820360a0850152611f3c8282611e2b565b91505060c0820151611f5960c08501826001600160a01b03169052565b5060e082015160e08401526101008201516101008401526101208201516101208401526101408201519150828103610140840152611f9781836116e8565b925050506080840151601f198483030160a08501526112e282826116e8565b6001600160e01b03198316815281515f908060208501600485015e5f92016004019182525092915050565b634e487b7160e01b5f52601260045260245ffdfea164736f6c634300081e000a0000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b50000000000000000000000002f577a41bec1be1152aeea12e73b7391d15f655d000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + )), + constructor_args: None, + creation_bytecode: None, + verify_json_path: None, + }, // Add more contracts here: // ContractSpec { // name: "MyNewContract", diff --git a/src/helpers/pre_conditions.rs b/src/helpers/pre_conditions.rs index 75ae83a..8efeacd 100644 --- a/src/helpers/pre_conditions.rs +++ b/src/helpers/pre_conditions.rs @@ -43,13 +43,19 @@ pub fn check_before(contract_to_deploy: Option<&ContractSpec>, args: &CliArgs) { info!("verify_json_path contract: {:?}", spec.verify_json_path); info!("salt contract: {:?}", spec.salt); - match verify_create2_address(spec) { - Ok(addr) => { - info!("CREATE2 address verified: {addr}"); + if spec.deployer_tx.is_some() { + if let Some(addr) = spec.address { + info!("Contract address (pre-built tx): {addr}"); } - Err(e) => { - error!("CREATE2 verification failed: {e}"); - exit(1); + } else { + match verify_create2_address(spec) { + Ok(addr) => { + info!("CREATE2 address verified: {addr}"); + } + Err(e) => { + error!("CREATE2 verification failed: {e}"); + exit(1); + } } } } else if args.contract_name.is_some() || args.address.is_some() || args.contract_path.is_some() diff --git a/src/main.rs b/src/main.rs index e5bf711..370519e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ use log::{Level, error, warn}; use std::env::var; use std::process::exit; -use deterministic_deployer_evm::utils::print_intro::print_intro_log; use deterministic_deployer_evm::client::wallet_client::create_deployers; use deterministic_deployer_evm::data::contracts::create_contract_spec_from_args; use deterministic_deployer_evm::helpers::contract_searcher::resolve_contract; @@ -16,6 +15,7 @@ use deterministic_deployer_evm::types::constants::Constants; use deterministic_deployer_evm::types::errors::CliError; use deterministic_deployer_evm::utils::create_keystore::load_or_create_keystore; use deterministic_deployer_evm::utils::deploy::run_deployments; +use deterministic_deployer_evm::utils::print_intro::print_intro_log; use deterministic_deployer_evm::utils::read_buf::parse_args; use deterministic_deployer_evm::utils::verifier::run_verifications; @@ -100,6 +100,6 @@ async fn main() { ready_for_verify.extend(deployed); if args.verify { - run_verifications(&ready_for_verify, &spec).await; + run_verifications(&ready_for_verify, &spec, args.source_chain.as_deref()).await; } } diff --git a/src/types/config.rs b/src/types/config.rs index 70fb4e7..3edaa0f 100644 --- a/src/types/config.rs +++ b/src/types/config.rs @@ -61,6 +61,24 @@ pub struct EtherscanResponse { pub result: String, } +#[derive(Deserialize)] +pub struct SourceCodeResult { + #[serde(rename = "SourceCode")] + pub source_code: String, + #[serde(rename = "ContractName")] + pub contract_name: String, + #[serde(rename = "CompilerVersion")] + pub compiler_version: String, + #[serde(rename = "ConstructorArguments")] + pub constructor_arguments: String, +} + +#[derive(Deserialize)] +pub struct GetSourceCodeResponse { + pub status: String, + pub result: Vec, +} + pub struct CliArgs { pub contract_path: Option, pub chains: Vec, @@ -70,6 +88,7 @@ pub struct CliArgs { pub verify: bool, pub constructor_args: Option, pub keystore: bool, + pub source_chain: Option, } pub struct PrecheckResult { diff --git a/src/types/errors.rs b/src/types/errors.rs index 844aae5..ffb2f9c 100644 --- a/src/types/errors.rs +++ b/src/types/errors.rs @@ -130,6 +130,8 @@ pub enum VerifierError { HttpError(&'static str, String), #[error("forge not found — install Foundry: {0}")] ForgeNotFound(String), + #[error("Contract not verified on source chain {0}")] + NotVerifiedOnSource(String), } #[derive(Debug, thiserror::Error)] diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index dec1c87..63f6fbd 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -28,12 +28,15 @@ pub async fn deploy_contract( .public() .ok_or_else(|| DeployError::NoProvider(wallet.address()))?; - let salt: FixedBytes<32> = spec.salt.ok_or(DeployError::MissingSalt(spec.name))?; - let init_code: Bytes = spec - .full_init_code() - .ok_or(DeployError::MissingInitCode(spec.name))?; - - let calldata: Bytes = build_calldata(&salt, &init_code); + let calldata: Bytes = if let Some(tx_data) = spec.deployer_tx { + Bytes::from_static(tx_data) + } else { + let salt: FixedBytes<32> = spec.salt.ok_or(DeployError::MissingSalt(spec.name))?; + let init_code: Bytes = spec + .full_init_code() + .ok_or(DeployError::MissingInitCode(spec.name))?; + build_calldata(&salt, &init_code) + }; let tx: TransactionRequest = TransactionRequest::default() .with_to(*Constants::DETERMINISTIC_DEPLOYER) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6103946..d082050 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,6 +4,6 @@ pub mod create_keystore; pub mod deploy; pub mod init_explorers; pub mod init_rpc; +pub mod print_intro; pub mod read_buf; pub mod verifier; -pub mod print_intro; \ No newline at end of file diff --git a/src/utils/print_intro.rs b/src/utils/print_intro.rs index 5491aff..d6dd9e2 100644 --- a/src/utils/print_intro.rs +++ b/src/utils/print_intro.rs @@ -1,4 +1,4 @@ -const INTRO: &'static str = r#" +const INTRO: &str = r" ___________________ / Deploy \ █████ █████ ███ ███ █████ ███ ___________ / Contract in one \ ░░███ ░░███ ░░░ ░░░ ░░███ ░░░ @@ -22,9 +22,8 @@ const INTRO: &'static str = r#" /_____| |_____\ ░░░░░ ░░░░░░ ___| | | |___ (_______| |_______) -"#; - +"; pub fn print_intro_log() { println!("\x1b[32m{INTRO}\x1b[0m"); -} \ No newline at end of file +} diff --git a/src/utils/read_buf.rs b/src/utils/read_buf.rs index baaf650..00efeda 100644 --- a/src/utils/read_buf.rs +++ b/src/utils/read_buf.rs @@ -168,6 +168,7 @@ pub fn parse_args() -> Result { let mut verify: bool = false; let mut constructor_args: Option = None; let mut keystore: bool = false; + let mut source_chain: Option = None; let mut chains: Vec = Vec::with_capacity(Chain::COUNT); let mut seen: ChainSet = ChainSet::new(); let mut parser: lexopt::Parser = lexopt::Parser::from_env(); @@ -205,6 +206,15 @@ pub fn parse_args() -> Result { Long("keystore") => { keystore = true; } + Long("source-chain") => { + let val: OsString = parser + .value() + .map_err(|e| CliError::ParseError(e.to_string()))?; + let val_str = val + .to_str() + .ok_or_else(|| CliError::ParseError("invalid UTF-8".to_string()))?; + source_chain = Some(val_str.to_string()); + } Long("constructor-args") => { let val: OsString = parser .value() @@ -266,6 +276,7 @@ pub fn parse_args() -> Result { verify, constructor_args, keystore, + source_chain, }) } @@ -278,6 +289,9 @@ fn print_usage() { eprintln!(" --address Contract address (hex, with or without 0x)"); eprintln!(" --verify Enable contract verification"); eprintln!(" --keystore Create and use an encrypted keystore"); + eprintln!( + " --source-chain Chain with verified source (for cross-chain verification)" + ); eprintln!( " --constructor-args ABI-encoded constructor arguments (hex, with or without 0x)" ); diff --git a/src/utils/verifier.rs b/src/utils/verifier.rs index 997d44a..6dfacd6 100644 --- a/src/utils/verifier.rs +++ b/src/utils/verifier.rs @@ -1,8 +1,10 @@ +use serde_json::{Map, Value, from_str}; use std::borrow::Cow; use std::env::var; use std::fs::read_to_string; use std::process::Command; use std::process::Output; +use std::sync::Arc; use std::time::Duration; use alloy::primitives::{Address, hex}; @@ -12,13 +14,158 @@ use log::{error, info, warn}; use tokio::task::{JoinSet, spawn_blocking}; use tokio::time::sleep; -use crate::types::config::{ContractSpec, EtherscanResponse, PublicClient, WalletClient}; +use crate::types::config::{ + Chain, ContractSpec, EtherscanResponse, GetSourceCodeResponse, PublicClient, SourceCodeResult, + WalletClient, +}; use crate::types::constants::Constants; use crate::types::errors::VerifierError; const POLL_INTERVAL: Duration = Duration::from_secs(3); const MAX_POLL_ATTEMPTS: u32 = 20; +fn find_contract_file(source_json: &str, contract_name: &str) -> Option { + let parsed: Value = from_str(source_json).ok()?; + let sources: &Map = parsed.get("sources")?.as_object()?; + let suffix: String = format!("{contract_name}.sol"); + sources.keys().find(|k| k.ends_with(&suffix)).cloned() +} + +async fn fetch_verified_source( + api_key: &str, + chain_id: u64, + address: Address, + name: &'static str, +) -> Result { + let url: String = format!( + "{}?module=contract&action=getsourcecode&address={address}&chainid={chain_id}&apikey={api_key}", + Constants::ETHERSCAN_V2_URL + ); + + let client: Client = reqwest::Client::new(); + let resp: GetSourceCodeResponse = client + .get(&url) + .send() + .await + .map_err(|e| VerifierError::HttpError(name, e.to_string()))? + .json() + .await + .map_err(|e| VerifierError::HttpError(name, e.to_string()))?; + + if resp.status != "1" || resp.result.is_empty() { + return Err(VerifierError::NotVerifiedOnSource(format!( + "chain_id {chain_id}" + ))); + } + + let mut source: SourceCodeResult = resp + .result + .into_iter() + .next() + .ok_or_else(|| VerifierError::NotVerifiedOnSource(format!("chain_id {chain_id}")))?; + + if source.source_code.is_empty() || source.contract_name.is_empty() { + return Err(VerifierError::NotVerifiedOnSource(format!( + "chain_id {chain_id}" + ))); + } + + if source.source_code.starts_with("{{") && source.source_code.ends_with("}}") { + source.source_code = source.source_code[1..source.source_code.len() - 1].to_string(); + } + + if !source.contract_name.contains(':') { + let contract_file: Option = + find_contract_file(&source.source_code, &source.contract_name); + if let Some(file) = contract_file { + source.contract_name = format!("{file}:{}", source.contract_name); + } + } + + Ok(source) +} + +async fn verify_cross_chain( + api_key: &str, + source: &SourceCodeResult, + target_chain_id: u64, + address: Address, + name: &'static str, + chain: &str, +) -> Result { + let client: Client = reqwest::Client::new(); + let submit_url: String = format!( + "{}?module=contract&action=verifysourcecode&chainid={target_chain_id}&apikey={api_key}", + Constants::ETHERSCAN_V2_URL + ); + + let addr_str: String = format!("{address}"); + let body: String = build_form_body(&[ + ("contractaddress", &addr_str), + ("sourceCode", &source.source_code), + ("codeformat", "solidity-standard-json-input"), + ("contractname", &source.contract_name), + ("compilerversion", &source.compiler_version), + ("constructorArguements", &source.constructor_arguments), + ]); + + let resp: EtherscanResponse = client + .post(&submit_url) + .header("Content-Type", "application/x-www-form-urlencoded") + .body(body) + .send() + .await + .map_err(|e| VerifierError::HttpError(name, e.to_string()))? + .json() + .await + .map_err(|e| VerifierError::HttpError(name, e.to_string()))?; + + if resp.status != "1" { + return Err(VerifierError::SubmissionFailed(name, resp.result)); + } + + let guid: String = resp.result; + warn!("Cross-chain verification submitted for '{name}' on {chain} (guid: {guid})"); + + poll_verification_status(&client, target_chain_id, api_key, &guid, name).await +} + +async fn poll_verification_status( + client: &Client, + chain_id: u64, + api_key: &str, + guid: &str, + name: &'static str, +) -> Result { + let status_url: String = format!( + "{}?module=contract&action=checkverifystatus&guid={guid}&chainid={chain_id}&apikey={api_key}", + Constants::ETHERSCAN_V2_URL + ); + + for _ in 0..MAX_POLL_ATTEMPTS { + sleep(POLL_INTERVAL).await; + + let status: EtherscanResponse = client + .get(&status_url) + .send() + .await + .map_err(|e| VerifierError::HttpError(name, e.to_string()))? + .json() + .await + .map_err(|e| VerifierError::HttpError(name, e.to_string()))?; + + if status.result.contains("Pass") || status.result.contains("Already Verified") { + return Ok(status.result); + } + + if status.result.contains("Fail") { + return Err(VerifierError::VerificationFailed(name, status.result)); + } + } + + Err(VerifierError::Timeout(name, guid.to_string())) +} + fn url_encode(s: &str) -> String { let mut out: String = String::with_capacity(s.len() * 3); for b in s.bytes() { @@ -233,36 +380,14 @@ async fn verify_via_etherscan_api( spec.name ); - let status_url: String = format!( - "{}?module=contract&action=checkverifystatus&guid={guid}&chainid={chain_id}&apikey={api_key}", - Constants::ETHERSCAN_V2_URL - ); - - for _ in 0..MAX_POLL_ATTEMPTS { - sleep(POLL_INTERVAL).await; - - let status: EtherscanResponse = client - .get(&status_url) - .send() - .await - .map_err(|e| VerifierError::HttpError(spec.name, e.to_string()))? - .json() - .await - .map_err(|e| VerifierError::HttpError(spec.name, e.to_string()))?; - - if status.result.contains("Pass") || status.result.contains("Already Verified") { - return Ok(status.result); - } - - if status.result.contains("Fail") { - return Err(VerifierError::VerificationFailed(spec.name, status.result)); - } - } - - Err(VerifierError::Timeout(spec.name, guid)) + poll_verification_status(&client, chain_id, api_key, &guid, spec.name).await } -pub async fn run_verifications(ready_for_verify: &[WalletClient], spec: &ContractSpec) { +pub async fn run_verifications( + ready_for_verify: &[WalletClient], + spec: &ContractSpec, + source_chain: Option<&str>, +) { if ready_for_verify.is_empty() { return; } @@ -280,6 +405,35 @@ pub async fn run_verifications(ready_for_verify: &[WalletClient], spec: &Contrac let api_key: Option = var(Constants::ETHERSCAN_API_KEY_ENV).ok(); + let cross_chain_source: Option> = + if let (None, Some(addr), Some(key)) = (path, address, &api_key) { + let source_chain_id: Option = source_chain.and_then(|sc| { + let chain = Chain::from_flag(sc)?; + etherscan_chain_id(chain.as_rpc_key(), chain.network()) + }); + + if let Some(scid) = source_chain_id { + info!("Fetching verified source from source chain (id: {scid})"); + match fetch_verified_source(key, scid, addr, name).await { + Ok(source) => { + info!( + "Fetched source: {} ({})", + source.contract_name, source.compiler_version + ); + Some(Arc::new(source)) + } + Err(e) => { + warn!("Failed to fetch source from source chain: {e}"); + None + } + } + } else { + None + } + } else { + None + }; + let mut verify_set: JoinSet<()> = JoinSet::new(); for (i, deployer) in ready_for_verify.iter().enumerate() { @@ -292,6 +446,7 @@ pub async fn run_verifications(ready_for_verify: &[WalletClient], spec: &Contrac let chain_id: Option = etherscan_chain_id(chain_key, network); let api_key: Option = api_key.clone(); let delay: u64 = i as u64; + let cross_source: Option> = cross_chain_source.clone(); verify_set.spawn(async move { if delay > 0 { @@ -308,6 +463,10 @@ pub async fn run_verifications(ready_for_verify: &[WalletClient], spec: &Contrac .map_err(|e| VerifierError::VerificationFailed(name, e.to_string())) .and_then(|r| r) } + (Some(addr), None, Some(cid), Some(key)) if cross_source.is_some() => { + let source = cross_source.unwrap(); + verify_cross_chain(&key, &source, cid, addr, name, &chain).await + } (None, _, _, _) => Err(VerifierError::MissingAddress(name)), (_, None, _, _) => Err(VerifierError::MissingContractPath(name)), (_, _, None, _) => Err(VerifierError::UnsupportedChain(chain.clone())),