From 05da735111770e3c348c907f64bf005fbbc09533 Mon Sep 17 00:00:00 2001 From: Felipe Selmo Date: Wed, 4 May 2022 21:11:36 -0600 Subject: [PATCH] Add tests for CCIP Read offchain resolution --- ens/contract_data.py | 6 +- tests/core/contracts/test_offchain_lookup.py | 120 ++++++++ tests/ens/test_offchain_resolution.py | 16 +- tests/integration/conftest.py | 12 + .../generate_fixtures/go_ethereum.py | 29 +- tests/integration/geth-1.10.17-fixture.zip | Bin 42372 -> 48709 bytes tests/integration/go_ethereum/conftest.py | 5 + tests/integration/test_ethereum_tester.py | 15 + .../_utils}/contract_sources/Emitter.sol | 0 .../_utils}/contract_sources/Emitter_old.sol | 0 .../contract_sources}/ExtendedResolver.sol | 0 .../contract_sources/OffchainLookup.sol | 54 ++++ .../contract_sources}/OffchainResolver.sol | 10 +- .../contract_sources}/SimpleResolver.sol | 0 .../module_testing/emitter_contract_old.py | 2 + web3/_utils/module_testing/eth_module.py | 286 +++++++++++++++--- .../offchain_lookup_contract.py | 104 +++++++ web3/_utils/module_testing/utils.py | 158 ++++++++++ web3/providers/eth_tester/defaults.py | 4 +- 19 files changed, 763 insertions(+), 58 deletions(-) create mode 100644 tests/core/contracts/test_offchain_lookup.py rename {tests/core/contracts => web3/_utils}/contract_sources/Emitter.sol (100%) rename {tests/core/contracts => web3/_utils}/contract_sources/Emitter_old.sol (100%) rename {tests/ens/test_contracts => web3/_utils/contract_sources}/ExtendedResolver.sol (100%) create mode 100644 web3/_utils/contract_sources/OffchainLookup.sol rename {tests/ens/test_contracts => web3/_utils/contract_sources}/OffchainResolver.sol (96%) rename {tests/ens/test_contracts => web3/_utils/contract_sources}/SimpleResolver.sol (100%) create mode 100644 web3/_utils/module_testing/offchain_lookup_contract.py create mode 100644 web3/_utils/module_testing/utils.py diff --git a/ens/contract_data.py b/ens/contract_data.py index 53ff9f6443..596393a389 100644 --- a/ens/contract_data.py +++ b/ens/contract_data.py @@ -21,19 +21,19 @@ reverse_resolver_bytecode_runtime = '608060405234801561001057600080fd5b506004361061004c5760003560e01c806301ffc9a7146100515780633e9ce794146100815780639061b9231461009d578063f86bc879146100cd575b600080fd5b61006b6004803603810190610066919061051e565b6100fd565b6040516100789190610566565b60405180910390f35b61009b60048036038101906100969190610641565b61015e565b005b6100b760048036038101906100b291906106f9565b610245565b6040516100c49190610813565b60405180910390f35b6100e760048036038101906100e29190610835565b61042f565b6040516100f49190610566565b60405180910390f35b6000639061b92360e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061015757506101568261046b565b5b9050919050565b806001600085815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055507fe1c5610a6e0cbe10764ecd182adcef1ec338dc4e199c99c32ce98f38e12791df8333848460405161023894939291906108a6565b60405180910390a1505050565b60606040518060400160405280601781526020017f11657874656e6465642d7265736f6c766572036574680000000000000000000081525080519060200120858560405161029492919061092a565b60405180910390201480156102ad575060248383905010155b15610328577ff0a378cc2afe91730d0105e67d6bb037cc5b8b6bfec5b5962d9b637ff6497e5560001b83836004906024926102ea9392919061094d565b906102f591906109a0565b146102ff57600080fd5b61beef60405160200161031291906109ff565b6040516020818303038152906040529050610427565b60008585600081811061033e5761033d610a1a565b5b9050013560f81c60f81b60f81c60ff1690506040518060400160405280601781526020017f11657874656e6465642d7265736f6c766572036574680000000000000000000081525080519060200120868683600161039c9190610a82565b9080926103ab9392919061094d565b6040516103b9929190610ad8565b604051809103902014610401576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103f890610b28565b60405180910390fd5b61dead60405160200161041491906109ff565b6040516020818303038152906040529150505b949350505050565b6001602052826000526040600020602052816000526040600020602052806000526040600020600092509250509054906101000a900460ff1681565b60006301ffc9a760e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b600080fd5b600080fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6104fb816104c6565b811461050657600080fd5b50565b600081359050610518816104f2565b92915050565b600060208284031215610534576105336104bc565b5b600061054284828501610509565b91505092915050565b60008115159050919050565b6105608161054b565b82525050565b600060208201905061057b6000830184610557565b92915050565b6000819050919050565b61059481610581565b811461059f57600080fd5b50565b6000813590506105b18161058b565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006105e2826105b7565b9050919050565b6105f2816105d7565b81146105fd57600080fd5b50565b60008135905061060f816105e9565b92915050565b61061e8161054b565b811461062957600080fd5b50565b60008135905061063b81610615565b92915050565b60008060006060848603121561065a576106596104bc565b5b6000610668868287016105a2565b935050602061067986828701610600565b925050604061068a8682870161062c565b9150509250925092565b600080fd5b600080fd5b600080fd5b60008083601f8401126106b9576106b8610694565b5b8235905067ffffffffffffffff8111156106d6576106d5610699565b5b6020830191508360018202830111156106f2576106f161069e565b5b9250929050565b60008060008060408587031215610713576107126104bc565b5b600085013567ffffffffffffffff811115610731576107306104c1565b5b61073d878288016106a3565b9450945050602085013567ffffffffffffffff8111156107605761075f6104c1565b5b61076c878288016106a3565b925092505092959194509250565b600081519050919050565b600082825260208201905092915050565b60005b838110156107b4578082015181840152602081019050610799565b838111156107c3576000848401525b50505050565b6000601f19601f8301169050919050565b60006107e58261077a565b6107ef8185610785565b93506107ff818560208601610796565b610808816107c9565b840191505092915050565b6000602082019050818103600083015261082d81846107da565b905092915050565b60008060006060848603121561084e5761084d6104bc565b5b600061085c868287016105a2565b935050602061086d86828701610600565b925050604061087e86828701610600565b9150509250925092565b61089181610581565b82525050565b6108a0816105d7565b82525050565b60006080820190506108bb6000830187610888565b6108c86020830186610897565b6108d56040830185610897565b6108e26060830184610557565b95945050505050565b600081905092915050565b82818337600083830152505050565b600061091183856108eb565b935061091e8385846108f6565b82840190509392505050565b6000610937828486610905565b91508190509392505050565b600080fd5b600080fd5b6000808585111561096157610960610943565b5b8386111561097257610971610948565b5b6001850283019150848603905094509492505050565b600082905092915050565b600082821b905092915050565b60006109ac8383610988565b826109b78135610581565b925060208210156109f7576109f27fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83602003600802610993565b831692505b505092915050565b6000602082019050610a146000830184610897565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610a8d82610a49565b9150610a9883610a49565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610acd57610acc610a53565b5b828201905092915050565b6000610ae5828486610905565b91508190509392505050565b600082825260208201905092915050565b50565b6000610b12600083610af1565b9150610b1d82610b02565b600082019050919050565b60006020820190508181036000830152610b4181610b05565b905091905056fea264697066735822122053f3072486d953b17f72555d93bae38d3607f573e572c03ca3b10c082471640064736f6c634300080d0033' # ENSIP-10 - Extended Resolver for Wildcard Resolution -# compiled from `tests/test_contracts/ExtendedResolver.sol` +# compiled from `web3/_utils/contract_sources/ExtendedResolver.sol` extended_resolver_abi = '[{"inputs": [{"internalType": "contract ENS","name": "_ens","type": "address"}],"stateMutability": "nonpayable","type": "constructor"},{"anonymous": false,"inputs": [{"indexed": false,"internalType": "bytes32","name": "node","type": "bytes32"},{"indexed": false,"internalType": "address","name": "owner","type": "address"},{"indexed": false,"internalType": "address","name": "target","type": "address"},{"indexed": false,"internalType": "bool","name": "isAuthorised","type": "bool"}],"name": "AuthorisationChanged","type": "event"},{"inputs": [{"internalType": "bytes32", "name": "", "type": "bytes32"},{"internalType": "address", "name": "", "type": "address"},{"internalType": "address", "name": "", "type": "address"}],"name": "authorisations","outputs": [{"internalType": "bool", "name": "", "type": "bool"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "bytes", "name": "dnsName", "type": "bytes"},{"internalType": "bytes", "name": "data", "type": "bytes"}],"name": "resolve","outputs": [{"internalType": "bytes", "name": "", "type": "bytes"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "bytes32", "name": "node", "type": "bytes32"},{"internalType": "address", "name": "target", "type": "address"},{"internalType": "bool", "name": "isAuthorised", "type": "bool"}],"name": "setAuthorisation","outputs": [],"stateMutability": "nonpayable","type": "function"},{"inputs": [{"internalType": "bytes4","name": "interfaceID","type": "bytes4"}],"name": "supportsInterface","outputs": [{"internalType": "bool", "name": "", "type": "bool"}],"stateMutability": "pure","type": "function"}]' extended_resolver_bytecode = '608060405234801561001057600080fd5b50604051610dbb380380610dbb833981810160405281019061003291906100ed565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505061011a565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100a88261007d565b9050919050565b60006100ba8261009d565b9050919050565b6100ca816100af565b81146100d557600080fd5b50565b6000815190506100e7816100c1565b92915050565b60006020828403121561010357610102610078565b5b6000610111848285016100d8565b91505092915050565b610c92806101296000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806301ffc9a7146100515780633e9ce794146100815780639061b9231461009d578063f86bc879146100cd575b600080fd5b61006b60048036038101906100669190610554565b6100fd565b604051610078919061059c565b60405180910390f35b61009b60048036038101906100969190610677565b61015e565b005b6100b760048036038101906100b2919061072f565b610245565b6040516100c49190610849565b60405180910390f35b6100e760048036038101906100e2919061086b565b610465565b6040516100f4919061059c565b60405180910390f35b6000639061b92360e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806101575750610156826104a1565b5b9050919050565b806001600085815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055507fe1c5610a6e0cbe10764ecd182adcef1ec338dc4e199c99c32ce98f38e12791df8333848460405161023894939291906108dc565b60405180910390a1505050565b60606040518060400160405280601781526020017f11657874656e6465642d7265736f6c7665720365746800000000000000000000815250805190602001208585604051610294929190610960565b60405180910390201480156102ad575060248383905010155b1561035e577ff0a378cc2afe91730d0105e67d6bb037cc5b8b6bfec5b5962d9b637ff6497e5560001b83836004906024926102ea93929190610983565b906102f591906109d6565b14610335576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161032c90610ab8565b60405180910390fd5b61beef6040516020016103489190610ad8565b604051602081830303815290604052905061045d565b60008585600081811061037457610373610af3565b5b9050013560f81c60f81b60f81c60ff1690506040518060400160405280601781526020017f11657874656e6465642d7265736f6c76657203657468000000000000000000008152508051906020012086868360016103d29190610b5b565b9080926103e193929190610983565b6040516103ef929190610bb1565b604051809103902014610437576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161042e90610c3c565b60405180910390fd5b61dead60405160200161044a9190610ad8565b6040516020818303038152906040529150505b949350505050565b6001602052826000526040600020602052816000526040600020602052806000526040600020600092509250509054906101000a900460ff1681565b60006301ffc9a760e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b600080fd5b600080fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610531816104fc565b811461053c57600080fd5b50565b60008135905061054e81610528565b92915050565b60006020828403121561056a576105696104f2565b5b60006105788482850161053f565b91505092915050565b60008115159050919050565b61059681610581565b82525050565b60006020820190506105b1600083018461058d565b92915050565b6000819050919050565b6105ca816105b7565b81146105d557600080fd5b50565b6000813590506105e7816105c1565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610618826105ed565b9050919050565b6106288161060d565b811461063357600080fd5b50565b6000813590506106458161061f565b92915050565b61065481610581565b811461065f57600080fd5b50565b6000813590506106718161064b565b92915050565b6000806000606084860312156106905761068f6104f2565b5b600061069e868287016105d8565b93505060206106af86828701610636565b92505060406106c086828701610662565b9150509250925092565b600080fd5b600080fd5b600080fd5b60008083601f8401126106ef576106ee6106ca565b5b8235905067ffffffffffffffff81111561070c5761070b6106cf565b5b602083019150836001820283011115610728576107276106d4565b5b9250929050565b60008060008060408587031215610749576107486104f2565b5b600085013567ffffffffffffffff811115610767576107666104f7565b5b610773878288016106d9565b9450945050602085013567ffffffffffffffff811115610796576107956104f7565b5b6107a2878288016106d9565b925092505092959194509250565b600081519050919050565b600082825260208201905092915050565b60005b838110156107ea5780820151818401526020810190506107cf565b838111156107f9576000848401525b50505050565b6000601f19601f8301169050919050565b600061081b826107b0565b61082581856107bb565b93506108358185602086016107cc565b61083e816107ff565b840191505092915050565b600060208201905081810360008301526108638184610810565b905092915050565b600080600060608486031215610884576108836104f2565b5b6000610892868287016105d8565b93505060206108a386828701610636565b92505060406108b486828701610636565b9150509250925092565b6108c7816105b7565b82525050565b6108d68161060d565b82525050565b60006080820190506108f160008301876108be565b6108fe60208301866108cd565b61090b60408301856108cd565b610918606083018461058d565b95945050505050565b600081905092915050565b82818337600083830152505050565b60006109478385610921565b935061095483858461092c565b82840190509392505050565b600061096d82848661093b565b91508190509392505050565b600080fd5b600080fd5b6000808585111561099757610996610979565b5b838611156109a8576109a761097e565b5b6001850283019150848603905094509492505050565b600082905092915050565b600082821b905092915050565b60006109e283836109be565b826109ed81356105b7565b92506020821015610a2d57610a287fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff836020036008026109c9565b831692505b505092915050565b600082825260208201905092915050565b7f706172656e7420646f6d61696e206e6f742076616c696461746564206170707260008201527f6f7072696174656c790000000000000000000000000000000000000000000000602082015250565b6000610aa2602983610a35565b9150610aad82610a46565b604082019050919050565b60006020820190508181036000830152610ad181610a95565b9050919050565b6000602082019050610aed60008301846108cd565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610b6682610b22565b9150610b7183610b22565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610ba657610ba5610b2c565b5b828201905092915050565b6000610bbe82848661093b565b91508190509392505050565b7f737562646f6d61696e206e6f742076616c69646174656420617070726f70726960008201527f6174656c79000000000000000000000000000000000000000000000000000000602082015250565b6000610c26602583610a35565b9150610c3182610bca565b604082019050919050565b60006020820190508181036000830152610c5581610c19565b905091905056fea26469706673582212209fff87be19ed85e754d5e2791de6ed052e7e653185e1d47fa94515f851becd2f64736f6c634300080d0033' extended_resolver_bytecode_runtime = '608060405234801561001057600080fd5b506004361061004c5760003560e01c806301ffc9a7146100515780633e9ce794146100815780639061b9231461009d578063f86bc879146100cd575b600080fd5b61006b60048036038101906100669190610554565b6100fd565b604051610078919061059c565b60405180910390f35b61009b60048036038101906100969190610677565b61015e565b005b6100b760048036038101906100b2919061072f565b610245565b6040516100c49190610849565b60405180910390f35b6100e760048036038101906100e2919061086b565b610465565b6040516100f4919061059c565b60405180910390f35b6000639061b92360e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806101575750610156826104a1565b5b9050919050565b806001600085815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055507fe1c5610a6e0cbe10764ecd182adcef1ec338dc4e199c99c32ce98f38e12791df8333848460405161023894939291906108dc565b60405180910390a1505050565b60606040518060400160405280601781526020017f11657874656e6465642d7265736f6c7665720365746800000000000000000000815250805190602001208585604051610294929190610960565b60405180910390201480156102ad575060248383905010155b1561035e577ff0a378cc2afe91730d0105e67d6bb037cc5b8b6bfec5b5962d9b637ff6497e5560001b83836004906024926102ea93929190610983565b906102f591906109d6565b14610335576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161032c90610ab8565b60405180910390fd5b61beef6040516020016103489190610ad8565b604051602081830303815290604052905061045d565b60008585600081811061037457610373610af3565b5b9050013560f81c60f81b60f81c60ff1690506040518060400160405280601781526020017f11657874656e6465642d7265736f6c76657203657468000000000000000000008152508051906020012086868360016103d29190610b5b565b9080926103e193929190610983565b6040516103ef929190610bb1565b604051809103902014610437576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161042e90610c3c565b60405180910390fd5b61dead60405160200161044a9190610ad8565b6040516020818303038152906040529150505b949350505050565b6001602052826000526040600020602052816000526040600020602052806000526040600020600092509250509054906101000a900460ff1681565b60006301ffc9a760e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b600080fd5b600080fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610531816104fc565b811461053c57600080fd5b50565b60008135905061054e81610528565b92915050565b60006020828403121561056a576105696104f2565b5b60006105788482850161053f565b91505092915050565b60008115159050919050565b61059681610581565b82525050565b60006020820190506105b1600083018461058d565b92915050565b6000819050919050565b6105ca816105b7565b81146105d557600080fd5b50565b6000813590506105e7816105c1565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610618826105ed565b9050919050565b6106288161060d565b811461063357600080fd5b50565b6000813590506106458161061f565b92915050565b61065481610581565b811461065f57600080fd5b50565b6000813590506106718161064b565b92915050565b6000806000606084860312156106905761068f6104f2565b5b600061069e868287016105d8565b93505060206106af86828701610636565b92505060406106c086828701610662565b9150509250925092565b600080fd5b600080fd5b600080fd5b60008083601f8401126106ef576106ee6106ca565b5b8235905067ffffffffffffffff81111561070c5761070b6106cf565b5b602083019150836001820283011115610728576107276106d4565b5b9250929050565b60008060008060408587031215610749576107486104f2565b5b600085013567ffffffffffffffff811115610767576107666104f7565b5b610773878288016106d9565b9450945050602085013567ffffffffffffffff811115610796576107956104f7565b5b6107a2878288016106d9565b925092505092959194509250565b600081519050919050565b600082825260208201905092915050565b60005b838110156107ea5780820151818401526020810190506107cf565b838111156107f9576000848401525b50505050565b6000601f19601f8301169050919050565b600061081b826107b0565b61082581856107bb565b93506108358185602086016107cc565b61083e816107ff565b840191505092915050565b600060208201905081810360008301526108638184610810565b905092915050565b600080600060608486031215610884576108836104f2565b5b6000610892868287016105d8565b93505060206108a386828701610636565b92505060406108b486828701610636565b9150509250925092565b6108c7816105b7565b82525050565b6108d68161060d565b82525050565b60006080820190506108f160008301876108be565b6108fe60208301866108cd565b61090b60408301856108cd565b610918606083018461058d565b95945050505050565b600081905092915050565b82818337600083830152505050565b60006109478385610921565b935061095483858461092c565b82840190509392505050565b600061096d82848661093b565b91508190509392505050565b600080fd5b600080fd5b6000808585111561099757610996610979565b5b838611156109a8576109a761097e565b5b6001850283019150848603905094509492505050565b600082905092915050565b600082821b905092915050565b60006109e283836109be565b826109ed81356105b7565b92506020821015610a2d57610a287fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff836020036008026109c9565b831692505b505092915050565b600082825260208201905092915050565b7f706172656e7420646f6d61696e206e6f742076616c696461746564206170707260008201527f6f7072696174656c790000000000000000000000000000000000000000000000602082015250565b6000610aa2602983610a35565b9150610aad82610a46565b604082019050919050565b60006020820190508181036000830152610ad181610a95565b9050919050565b6000602082019050610aed60008301846108cd565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610b6682610b22565b9150610b7183610b22565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610ba657610ba5610b2c565b5b828201905092915050565b6000610bbe82848661093b565b91508190509392505050565b7f737562646f6d61696e206e6f742076616c69646174656420617070726f70726960008201527f6174656c79000000000000000000000000000000000000000000000000000000602082015250565b6000610c26602583610a35565b9150610c3182610bca565b604082019050919050565b60006020820190508181036000830152610c5581610c19565b905091905056fea26469706673582212209fff87be19ed85e754d5e2791de6ed052e7e653185e1d47fa94515f851becd2f64736f6c634300080d0033' # Simple resolver with no support for interfaces like getText(), etc -# compiled from `tests/test_contracts/SimpleResolver.sol` +# compiled from `web3/_utils/contract_sources/SimpleResolver.sol` simple_resolver_abi = json.loads('[{"inputs":[{"internalType":"bytes32","name":"nodeID","type":"bytes32"}],"name":"addr","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]') simple_resolver_bytecode = '608060405234801561001057600080fd5b5061028c806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806301ffc9a71461003b5780633b3b57de1461006b575b600080fd5b61005560048036038101906100509190610134565b61009b565b604051610062919061017c565b60405180910390f35b610085600480360381019061008091906101cd565b6100cd565b604051610092919061023b565b60405180910390f35b6000633b3b57de60e01b827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b6000309050919050565b600080fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610111816100dc565b811461011c57600080fd5b50565b60008135905061012e81610108565b92915050565b60006020828403121561014a576101496100d7565b5b60006101588482850161011f565b91505092915050565b60008115159050919050565b61017681610161565b82525050565b6000602082019050610191600083018461016d565b92915050565b6000819050919050565b6101aa81610197565b81146101b557600080fd5b50565b6000813590506101c7816101a1565b92915050565b6000602082840312156101e3576101e26100d7565b5b60006101f1848285016101b8565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610225826101fa565b9050919050565b6102358161021a565b82525050565b6000602082019050610250600083018461022c565b9291505056fea2646970667358221220e9d34e70193fa6e99d9be00bd7b1e4b63dc6fd9c84934a35350d8b2c4215974964736f6c634300080d0033' simple_resolver_bytecode_runtime = '608060405234801561001057600080fd5b50600436106100365760003560e01c806301ffc9a71461003b5780633b3b57de1461006b575b600080fd5b61005560048036038101906100509190610134565b61009b565b604051610062919061017c565b60405180910390f35b610085600480360381019061008091906101cd565b6100cd565b604051610092919061023b565b60405180910390f35b6000633b3b57de60e01b827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b6000309050919050565b600080fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610111816100dc565b811461011c57600080fd5b50565b60008135905061012e81610108565b92915050565b60006020828403121561014a576101496100d7565b5b60006101588482850161011f565b91505092915050565b60008115159050919050565b61017681610161565b82525050565b6000602082019050610191600083018461016d565b92915050565b6000819050919050565b6101aa81610197565b81146101b557600080fd5b50565b6000813590506101c7816101a1565b92915050565b6000602082840312156101e3576101e26100d7565b5b60006101f1848285016101b8565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610225826101fa565b9050919050565b6102358161021a565b82525050565b6000602082019050610250600083018461022c565b9291505056fea2646970667358221220e9d34e70193fa6e99d9be00bd7b1e4b63dc6fd9c84934a35350d8b2c4215974964736f6c634300080d0033' # Offchain Resolver for offchain resolution w/ CCIP Read as described in EIP-3668 -# compiled from `tests/test_contracts/OffchainResolver.sol` +# compiled from `web3/_utils/contract_sources/OffchainResolver.sol` offchain_resolver_abi = json.loads('[{"inputs":[{"internalType":"string[]","name":"_urls","type":"string[]"},{"internalType":"address[]","name":"_signers","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"string[]","name":"urls","type":"string[]"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes4","name":"callbackFunction","type":"bytes4"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"name":"OffchainLookup","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"signers","type":"address[]"}],"name":"NewSigners","type":"event"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint64","name":"expires","type":"uint64"},{"internalType":"bytes","name":"request","type":"bytes"},{"internalType":"bytes","name":"result","type":"bytes"}],"name":"makeSignatureHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"name","type":"bytes"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"resolve","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"response","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"name":"resolveWithProof","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"signers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"url","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]') offchain_resolver_bytecode = "60806040523480156200001157600080fd5b506040516200219038038062002190833981810160405281019062000037919062000511565b81600090805190602001906200004f929190620001a4565b5060005b81518110156200010b5760018060008484815181106200009c577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508080620001029062000813565b91505062000053565b5060018060003073ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055507fab0b9cc3a46b568cb08d985497cde8ab7e18892d01f58db7dc7f0d2af859b2d78160405162000194919062000619565b60405180910390a1505062000919565b828054828255906000526020600020908101928215620001f8579160200282015b82811115620001f7578251829080519060200190620001e69291906200020b565b5091602001919060010190620001c5565b5b5090506200020791906200029c565b5090565b8280546200021990620007a7565b90600052602060002090601f0160209004810192826200023d576000855562000289565b82601f106200025857805160ff191683800117855562000289565b8280016001018555821562000289579182015b82811115620002885782518255916020019190600101906200026b565b5b509050620002989190620002c4565b5090565b5b80821115620002c05760008181620002b69190620002e3565b506001016200029d565b5090565b5b80821115620002df576000816000905550600101620002c5565b5090565b508054620002f190620007a7565b6000825580601f1062000305575062000326565b601f016020900490600052602060002090810190620003259190620002c4565b5b50565b6000620003406200033a8462000666565b6200063d565b905080838252602082019050828560208602820111156200036057600080fd5b60005b8581101562000394578162000379888262000473565b84526020840193506020830192505060018101905062000363565b5050509392505050565b6000620003b5620003af8462000695565b6200063d565b90508083825260208201905082856020860282011115620003d557600080fd5b60005b858110156200042457815167ffffffffffffffff811115620003f957600080fd5b808601620004088982620004e4565b85526020850194506020840193505050600181019050620003d8565b5050509392505050565b6000620004456200043f84620006c4565b6200063d565b9050828152602081018484840111156200045e57600080fd5b6200046b84828562000771565b509392505050565b6000815190506200048481620008ff565b92915050565b600082601f8301126200049c57600080fd5b8151620004ae84826020860162000329565b91505092915050565b600082601f830112620004c957600080fd5b8151620004db8482602086016200039e565b91505092915050565b600082601f830112620004f657600080fd5b8151620005088482602086016200042e565b91505092915050565b600080604083850312156200052557600080fd5b600083015167ffffffffffffffff8111156200054057600080fd5b6200054e85828601620004b7565b925050602083015167ffffffffffffffff8111156200056c57600080fd5b6200057a858286016200048a565b9150509250929050565b60006200059283836200059e565b60208301905092915050565b620005a98162000733565b82525050565b6000620005bc826200070a565b620005c8818562000722565b9350620005d583620006fa565b8060005b838110156200060c578151620005f0888262000584565b9750620005fd8362000715565b925050600181019050620005d9565b5085935050505092915050565b60006020820190508181036000830152620006358184620005af565b905092915050565b6000620006496200065c565b9050620006578282620007dd565b919050565b6000604051905090565b600067ffffffffffffffff821115620006845762000683620008bf565b5b602082029050602081019050919050565b600067ffffffffffffffff821115620006b357620006b2620008bf565b5b602082029050602081019050919050565b600067ffffffffffffffff821115620006e257620006e1620008bf565b5b620006ed82620008ee565b9050602081019050919050565b6000819050602082019050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b6000620007408262000747565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60005b838110156200079157808201518184015260208101905062000774565b83811115620007a1576000848401525b50505050565b60006002820490506001821680620007c057607f821691505b60208210811415620007d757620007d662000890565b5b50919050565b620007e882620008ee565b810181811067ffffffffffffffff821117156200080a5762000809620008bf565b5b80604052505050565b6000620008208262000767565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82141562000856576200085562000861565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b6200090a8162000733565b81146200091657600080fd5b50565b61186780620009296000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c806301ffc9a7146100675780631dcfea0914610097578063736c0d5b146100c7578063796676be146100f75780639061b92314610127578063f4d4d2f814610157575b600080fd5b610081600480360381019061007c9190610cb9565b610187565b60405161008e91906111c9565b60405180910390f35b6100b160048036038101906100ac9190610c26565b610201565b6040516100be91906111e4565b60405180910390f35b6100e160048036038101906100dc9190610bfd565b610219565b6040516100ee91906111c9565b60405180910390f35b610111600480360381019061010c9190610dd6565b610239565b60405161011e91906112a1565b60405180910390f35b610141600480360381019061013c9190610ce2565b6102e5565b60405161014e919061127f565b60405180910390f35b610171600480360381019061016c9190610ce2565b6103b4565b60405161017e919061127f565b60405180910390f35b60007f9061b923000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806101fa57506101f982610462565b5b9050919050565b600061020f858585856104cc565b9050949350505050565b60016020528060005260406000206000915054906101000a900460ff1681565b6000818154811061024957600080fd5b90600052602060002001600091509050805461026490611541565b80601f016020809104026020016040519081016040528092919081815260200182805461029090611541565b80156102dd5780601f106102b2576101008083540402835291602001916102dd565b820191906000526020600020905b8154815290600101906020018083116102c057829003601f168201915b505050505081565b60606000639061b92360e01b868686866040516024016103089493929190611244565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090503060008263f4d4d2f860e01b846040517f556f18300000000000000000000000000000000000000000000000000000000081526004016103ab959493929190611161565b60405180910390fd5b60606000806103c585858989610513565b91509150600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610455576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161044c90611323565b60405180910390fd5b8092505050949350505050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b60008484848051906020012084805190602001206040516020016104f39493929190611108565b604051602081830303815290604052805190602001209050949350505050565b600060606000806000868681019061052b9190610d57565b925092509250600061058c61058630858d8d8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050886104cc565b836105a1565b90508084955095505050505094509492505050565b60008060006105b085856105c8565b915091506105bd8161064b565b819250505092915050565b60008060418351141561060a5760008060006020860151925060408601519150606086015160001a90506105fe8782858561099c565b94509450505050610644565b60408351141561063b576000806020850151915060408501519050610630868383610aa9565b935093505050610644565b60006002915091505b9250929050565b60006004811115610685577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8160048111156106be577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b14156106c957610999565b60016004811115610703577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b81600481111561073c577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b141561077d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610774906112c3565b60405180910390fd5b600260048111156107b7577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8160048111156107f0577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610831576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610828906112e3565b60405180910390fd5b6003600481111561086b577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8160048111156108a4577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b14156108e5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108dc90611303565b60405180910390fd5b60048081111561091e577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b816004811115610957577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610998576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098f90611343565b60405180910390fd5b5b50565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08360001c11156109d7576000600391509150610aa0565b601b8560ff16141580156109ef5750601c8560ff1614155b15610a01576000600491509150610aa0565b600060018787878760405160008152602001604052604051610a2694939291906111ff565b6020604051602081039080840390855afa158015610a48573d6000803e3d6000fd5b505050602060405103519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415610a9757600060019250925050610aa0565b80600092509250505b94509492505050565b6000806000807f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85169150601b8560ff1c019050610ae98782888561099c565b935093505050935093915050565b6000610b0a610b0584611388565b611363565b905082815260208101848484011115610b2257600080fd5b610b2d8482856114ff565b509392505050565b600081359050610b44816117d5565b92915050565b600081359050610b59816117ec565b92915050565b60008083601f840112610b7157600080fd5b8235905067ffffffffffffffff811115610b8a57600080fd5b602083019150836001820283011115610ba257600080fd5b9250929050565b600082601f830112610bba57600080fd5b8135610bca848260208601610af7565b91505092915050565b600081359050610be281611803565b92915050565b600081359050610bf78161181a565b92915050565b600060208284031215610c0f57600080fd5b6000610c1d84828501610b35565b91505092915050565b60008060008060808587031215610c3c57600080fd5b6000610c4a87828801610b35565b9450506020610c5b87828801610be8565b935050604085013567ffffffffffffffff811115610c7857600080fd5b610c8487828801610ba9565b925050606085013567ffffffffffffffff811115610ca157600080fd5b610cad87828801610ba9565b91505092959194509250565b600060208284031215610ccb57600080fd5b6000610cd984828501610b4a565b91505092915050565b60008060008060408587031215610cf857600080fd5b600085013567ffffffffffffffff811115610d1257600080fd5b610d1e87828801610b5f565b9450945050602085013567ffffffffffffffff811115610d3d57600080fd5b610d4987828801610b5f565b925092505092959194509250565b600080600060608486031215610d6c57600080fd5b600084013567ffffffffffffffff811115610d8657600080fd5b610d9286828701610ba9565b9350506020610da386828701610be8565b925050604084013567ffffffffffffffff811115610dc057600080fd5b610dcc86828701610ba9565b9150509250925092565b600060208284031215610de857600080fd5b6000610df684828501610bd3565b91505092915050565b6000610e0b8383610f90565b905092915050565b610e1c81611460565b82525050565b610e33610e2e82611460565b6115a4565b82525050565b6000610e44826113e3565b610e4e8185611411565b935083602082028501610e60856113b9565b8060005b85811015610e9b57848403895281610e7c8582610dff565b9450610e8783611404565b925060208a01995050600181019050610e64565b50829750879550505050505092915050565b610eb681611472565b82525050565b610ec58161147e565b82525050565b610edc610ed78261147e565b6115b6565b82525050565b610eeb81611488565b82525050565b6000610efd8385611422565b9350610f0a8385846114ff565b610f1383611642565b840190509392505050565b6000610f29826113ee565b610f338185611422565b9350610f4381856020860161150e565b610f4c81611642565b840191505092915050565b6000610f62826113f9565b610f6c8185611444565b9350610f7c81856020860161150e565b610f8581611642565b840191505092915050565b60008154610f9d81611541565b610fa78186611433565b94506001821660008114610fc25760018114610fd457611007565b60ff1983168652602086019350611007565b610fdd856113ce565b60005b83811015610fff57815481890152600182019150602081019050610fe0565b808801955050505b50505092915050565b600061101d601883611444565b91506110288261166d565b602082019050919050565b6000611040601f83611444565b915061104b82611696565b602082019050919050565b6000611063602283611444565b915061106e826116bf565b604082019050919050565b6000611086602483611444565b91506110918261170e565b604082019050919050565b60006110a9600283611455565b91506110b48261175d565b600282019050919050565b60006110cc602283611444565b91506110d782611786565b604082019050919050565b6110f36110ee826114de565b6115d2565b82525050565b611102816114f2565b82525050565b60006111138261109c565b915061111f8287610e22565b60148201915061112f82866110e2565b60088201915061113f8285610ecb565b60208201915061114f8284610ecb565b60208201915081905095945050505050565b600060a0820190506111766000830188610e13565b81810360208301526111888187610e39565b9050818103604083015261119c8186610f1e565b90506111ab6060830185610ee2565b81810360808301526111bd8184610f1e565b90509695505050505050565b60006020820190506111de6000830184610ead565b92915050565b60006020820190506111f96000830184610ebc565b92915050565b60006080820190506112146000830187610ebc565b61122160208301866110f9565b61122e6040830185610ebc565b61123b6060830184610ebc565b95945050505050565b6000604082019050818103600083015261125f818688610ef1565b90508181036020830152611274818486610ef1565b905095945050505050565b600060208201905081810360008301526112998184610f1e565b905092915050565b600060208201905081810360008301526112bb8184610f57565b905092915050565b600060208201905081810360008301526112dc81611010565b9050919050565b600060208201905081810360008301526112fc81611033565b9050919050565b6000602082019050818103600083015261131c81611056565b9050919050565b6000602082019050818103600083015261133c81611079565b9050919050565b6000602082019050818103600083015261135c816110bf565b9050919050565b600061136d61137e565b90506113798282611573565b919050565b6000604051905090565b600067ffffffffffffffff8211156113a3576113a2611613565b5b6113ac82611642565b9050602081019050919050565b60008190508160005260206000209050919050565b60008190508160005260206000209050919050565b600081549050919050565b600081519050919050565b600081519050919050565b6000600182019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600082825260208201905092915050565b600082825260208201905092915050565b600081905092915050565b600061146b826114b4565b9050919050565b60008115159050919050565b6000819050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600067ffffffffffffffff82169050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b8381101561152c578082015181840152602081019050611511565b8381111561153b576000848401525b50505050565b6000600282049050600182168061155957607f821691505b6020821081141561156d5761156c6115e4565b5b50919050565b61157c82611642565b810181811067ffffffffffffffff8211171561159b5761159a611613565b5b80604052505050565b60006115af826115c0565b9050919050565b6000819050919050565b60006115cb82611660565b9050919050565b60006115dd82611653565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b60008160c01b9050919050565b60008160601b9050919050565b7f45434453413a20696e76616c6964207369676e61747572650000000000000000600082015250565b7f45434453413a20696e76616c6964207369676e6174757265206c656e67746800600082015250565b7f45434453413a20696e76616c6964207369676e6174757265202773272076616c60008201527f7565000000000000000000000000000000000000000000000000000000000000602082015250565b7f5369676e617475726556657269666965723a20496e76616c6964207369676e6160008201527f7475726500000000000000000000000000000000000000000000000000000000602082015250565b7f1900000000000000000000000000000000000000000000000000000000000000600082015250565b7f45434453413a20696e76616c6964207369676e6174757265202776272076616c60008201527f7565000000000000000000000000000000000000000000000000000000000000602082015250565b6117de81611460565b81146117e957600080fd5b50565b6117f581611488565b811461180057600080fd5b50565b61180c816114d4565b811461181757600080fd5b50565b611823816114de565b811461182e57600080fd5b5056fea2646970667358221220e5f1e91619d2bc52c11e1bd99d0dabfdbeca91f7eac25e3122f6117fc623900b64736f6c63430008040033" offchain_resolver_bytecode_runtime = "608060405234801561001057600080fd5b50600436106100625760003560e01c806301ffc9a7146100675780631dcfea0914610097578063736c0d5b146100c7578063796676be146100f75780639061b92314610127578063f4d4d2f814610157575b600080fd5b610081600480360381019061007c9190610cb9565b610187565b60405161008e91906111c9565b60405180910390f35b6100b160048036038101906100ac9190610c26565b610201565b6040516100be91906111e4565b60405180910390f35b6100e160048036038101906100dc9190610bfd565b610219565b6040516100ee91906111c9565b60405180910390f35b610111600480360381019061010c9190610dd6565b610239565b60405161011e91906112a1565b60405180910390f35b610141600480360381019061013c9190610ce2565b6102e5565b60405161014e919061127f565b60405180910390f35b610171600480360381019061016c9190610ce2565b6103b4565b60405161017e919061127f565b60405180910390f35b60007f9061b923000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806101fa57506101f982610462565b5b9050919050565b600061020f858585856104cc565b9050949350505050565b60016020528060005260406000206000915054906101000a900460ff1681565b6000818154811061024957600080fd5b90600052602060002001600091509050805461026490611541565b80601f016020809104026020016040519081016040528092919081815260200182805461029090611541565b80156102dd5780601f106102b2576101008083540402835291602001916102dd565b820191906000526020600020905b8154815290600101906020018083116102c057829003601f168201915b505050505081565b60606000639061b92360e01b868686866040516024016103089493929190611244565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090503060008263f4d4d2f860e01b846040517f556f18300000000000000000000000000000000000000000000000000000000081526004016103ab959493929190611161565b60405180910390fd5b60606000806103c585858989610513565b91509150600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610455576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161044c90611323565b60405180910390fd5b8092505050949350505050565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b60008484848051906020012084805190602001206040516020016104f39493929190611108565b604051602081830303815290604052805190602001209050949350505050565b600060606000806000868681019061052b9190610d57565b925092509250600061058c61058630858d8d8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050886104cc565b836105a1565b90508084955095505050505094509492505050565b60008060006105b085856105c8565b915091506105bd8161064b565b819250505092915050565b60008060418351141561060a5760008060006020860151925060408601519150606086015160001a90506105fe8782858561099c565b94509450505050610644565b60408351141561063b576000806020850151915060408501519050610630868383610aa9565b935093505050610644565b60006002915091505b9250929050565b60006004811115610685577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8160048111156106be577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b14156106c957610999565b60016004811115610703577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b81600481111561073c577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b141561077d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610774906112c3565b60405180910390fd5b600260048111156107b7577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8160048111156107f0577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610831576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610828906112e3565b60405180910390fd5b6003600481111561086b577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8160048111156108a4577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b14156108e5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108dc90611303565b60405180910390fd5b60048081111561091e577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b816004811115610957577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b1415610998576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161098f90611343565b60405180910390fd5b5b50565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08360001c11156109d7576000600391509150610aa0565b601b8560ff16141580156109ef5750601c8560ff1614155b15610a01576000600491509150610aa0565b600060018787878760405160008152602001604052604051610a2694939291906111ff565b6020604051602081039080840390855afa158015610a48573d6000803e3d6000fd5b505050602060405103519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415610a9757600060019250925050610aa0565b80600092509250505b94509492505050565b6000806000807f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85169150601b8560ff1c019050610ae98782888561099c565b935093505050935093915050565b6000610b0a610b0584611388565b611363565b905082815260208101848484011115610b2257600080fd5b610b2d8482856114ff565b509392505050565b600081359050610b44816117d5565b92915050565b600081359050610b59816117ec565b92915050565b60008083601f840112610b7157600080fd5b8235905067ffffffffffffffff811115610b8a57600080fd5b602083019150836001820283011115610ba257600080fd5b9250929050565b600082601f830112610bba57600080fd5b8135610bca848260208601610af7565b91505092915050565b600081359050610be281611803565b92915050565b600081359050610bf78161181a565b92915050565b600060208284031215610c0f57600080fd5b6000610c1d84828501610b35565b91505092915050565b60008060008060808587031215610c3c57600080fd5b6000610c4a87828801610b35565b9450506020610c5b87828801610be8565b935050604085013567ffffffffffffffff811115610c7857600080fd5b610c8487828801610ba9565b925050606085013567ffffffffffffffff811115610ca157600080fd5b610cad87828801610ba9565b91505092959194509250565b600060208284031215610ccb57600080fd5b6000610cd984828501610b4a565b91505092915050565b60008060008060408587031215610cf857600080fd5b600085013567ffffffffffffffff811115610d1257600080fd5b610d1e87828801610b5f565b9450945050602085013567ffffffffffffffff811115610d3d57600080fd5b610d4987828801610b5f565b925092505092959194509250565b600080600060608486031215610d6c57600080fd5b600084013567ffffffffffffffff811115610d8657600080fd5b610d9286828701610ba9565b9350506020610da386828701610be8565b925050604084013567ffffffffffffffff811115610dc057600080fd5b610dcc86828701610ba9565b9150509250925092565b600060208284031215610de857600080fd5b6000610df684828501610bd3565b91505092915050565b6000610e0b8383610f90565b905092915050565b610e1c81611460565b82525050565b610e33610e2e82611460565b6115a4565b82525050565b6000610e44826113e3565b610e4e8185611411565b935083602082028501610e60856113b9565b8060005b85811015610e9b57848403895281610e7c8582610dff565b9450610e8783611404565b925060208a01995050600181019050610e64565b50829750879550505050505092915050565b610eb681611472565b82525050565b610ec58161147e565b82525050565b610edc610ed78261147e565b6115b6565b82525050565b610eeb81611488565b82525050565b6000610efd8385611422565b9350610f0a8385846114ff565b610f1383611642565b840190509392505050565b6000610f29826113ee565b610f338185611422565b9350610f4381856020860161150e565b610f4c81611642565b840191505092915050565b6000610f62826113f9565b610f6c8185611444565b9350610f7c81856020860161150e565b610f8581611642565b840191505092915050565b60008154610f9d81611541565b610fa78186611433565b94506001821660008114610fc25760018114610fd457611007565b60ff1983168652602086019350611007565b610fdd856113ce565b60005b83811015610fff57815481890152600182019150602081019050610fe0565b808801955050505b50505092915050565b600061101d601883611444565b91506110288261166d565b602082019050919050565b6000611040601f83611444565b915061104b82611696565b602082019050919050565b6000611063602283611444565b915061106e826116bf565b604082019050919050565b6000611086602483611444565b91506110918261170e565b604082019050919050565b60006110a9600283611455565b91506110b48261175d565b600282019050919050565b60006110cc602283611444565b91506110d782611786565b604082019050919050565b6110f36110ee826114de565b6115d2565b82525050565b611102816114f2565b82525050565b60006111138261109c565b915061111f8287610e22565b60148201915061112f82866110e2565b60088201915061113f8285610ecb565b60208201915061114f8284610ecb565b60208201915081905095945050505050565b600060a0820190506111766000830188610e13565b81810360208301526111888187610e39565b9050818103604083015261119c8186610f1e565b90506111ab6060830185610ee2565b81810360808301526111bd8184610f1e565b90509695505050505050565b60006020820190506111de6000830184610ead565b92915050565b60006020820190506111f96000830184610ebc565b92915050565b60006080820190506112146000830187610ebc565b61122160208301866110f9565b61122e6040830185610ebc565b61123b6060830184610ebc565b95945050505050565b6000604082019050818103600083015261125f818688610ef1565b90508181036020830152611274818486610ef1565b905095945050505050565b600060208201905081810360008301526112998184610f1e565b905092915050565b600060208201905081810360008301526112bb8184610f57565b905092915050565b600060208201905081810360008301526112dc81611010565b9050919050565b600060208201905081810360008301526112fc81611033565b9050919050565b6000602082019050818103600083015261131c81611056565b9050919050565b6000602082019050818103600083015261133c81611079565b9050919050565b6000602082019050818103600083015261135c816110bf565b9050919050565b600061136d61137e565b90506113798282611573565b919050565b6000604051905090565b600067ffffffffffffffff8211156113a3576113a2611613565b5b6113ac82611642565b9050602081019050919050565b60008190508160005260206000209050919050565b60008190508160005260206000209050919050565b600081549050919050565b600081519050919050565b600081519050919050565b6000600182019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600082825260208201905092915050565b600082825260208201905092915050565b600081905092915050565b600061146b826114b4565b9050919050565b60008115159050919050565b6000819050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600067ffffffffffffffff82169050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b8381101561152c578082015181840152602081019050611511565b8381111561153b576000848401525b50505050565b6000600282049050600182168061155957607f821691505b6020821081141561156d5761156c6115e4565b5b50919050565b61157c82611642565b810181811067ffffffffffffffff8211171561159b5761159a611613565b5b80604052505050565b60006115af826115c0565b9050919050565b6000819050919050565b60006115cb82611660565b9050919050565b60006115dd82611653565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b60008160c01b9050919050565b60008160601b9050919050565b7f45434453413a20696e76616c6964207369676e61747572650000000000000000600082015250565b7f45434453413a20696e76616c6964207369676e6174757265206c656e67746800600082015250565b7f45434453413a20696e76616c6964207369676e6174757265202773272076616c60008201527f7565000000000000000000000000000000000000000000000000000000000000602082015250565b7f5369676e617475726556657269666965723a20496e76616c6964207369676e6160008201527f7475726500000000000000000000000000000000000000000000000000000000602082015250565b7f1900000000000000000000000000000000000000000000000000000000000000600082015250565b7f45434453413a20696e76616c6964207369676e6174757265202776272076616c60008201527f7565000000000000000000000000000000000000000000000000000000000000602082015250565b6117de81611460565b81146117e957600080fd5b50565b6117f581611488565b811461180057600080fd5b50565b61180c816114d4565b811461181757600080fd5b50565b611823816114de565b811461182e57600080fd5b5056fea2646970667358221220e5f1e91619d2bc52c11e1bd99d0dabfdbeca91f7eac25e3122f6117fc623900b64736f6c63430008040033" diff --git a/tests/core/contracts/test_offchain_lookup.py b/tests/core/contracts/test_offchain_lookup.py new file mode 100644 index 0000000000..1f7c8a0035 --- /dev/null +++ b/tests/core/contracts/test_offchain_lookup.py @@ -0,0 +1,120 @@ +import pytest + +from eth_abi import ( + decode_abi, +) + +from web3._utils.module_testing.offchain_lookup_contract import ( + OFFCHAIN_LOOKUP_ABI, + OFFCHAIN_LOOKUP_BYTECODE, + OFFCHAIN_LOOKUP_BYTECODE_RUNTIME, +) +from web3._utils.module_testing.utils import ( + mock_offchain_lookup_request_response, +) +from web3._utils.type_conversion_utils import ( + to_hex_if_bytes, +) +from web3.exceptions import ( + TooManyRequests, +) + +# "test offchain lookup" as an abi-encoded string +OFFCHAIN_LOOKUP_TEST_DATA = '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501 +# "web3py" as an abi-encoded string +WEB3PY_AS_HEXBYTES = '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000067765623370790000000000000000000000000000000000000000000000000000' # noqa: E501 + + +@pytest.fixture +def OffchainLookup(w3): + # compiled from `web3/_utils/module_testing/contract_sources/OffchainLookup.sol` + return w3.eth.contract( + abi=OFFCHAIN_LOOKUP_ABI, + bytecode=OFFCHAIN_LOOKUP_BYTECODE, + bytecode_runtime=OFFCHAIN_LOOKUP_BYTECODE_RUNTIME, + ) + + +@pytest.fixture +def offchain_lookup_contract( + w3, wait_for_block, OffchainLookup, wait_for_transaction, address_conversion_func, +): + wait_for_block(w3) + deploy_txn_hash = OffchainLookup.constructor().transact({'gas': 10000000}) + deploy_receipt = wait_for_transaction(w3, deploy_txn_hash) + contract_address = address_conversion_func(deploy_receipt['contractAddress']) + + bytecode = w3.eth.get_code(contract_address) + assert bytecode == OffchainLookup.bytecode_runtime + deployed_offchain_lookup = OffchainLookup(address=contract_address) + assert deployed_offchain_lookup.address == contract_address + return deployed_offchain_lookup + + +def test_offchain_lookup_functionality( + offchain_lookup_contract, monkeypatch, +): + normalized_address = to_hex_if_bytes(offchain_lookup_contract.address) + mock_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501 + mocked_json_data=WEB3PY_AS_HEXBYTES, + ) + response = offchain_lookup_contract.caller.testOffchainLookup(OFFCHAIN_LOOKUP_TEST_DATA) + assert decode_abi(['string'], response)[0] == 'web3py' + + +@pytest.mark.parametrize('status_code_non_4xx_error', [100, 300, 500, 600]) +def test_eth_call_offchain_lookup_tries_next_url_for_non_4xx_error_status_and_tests_POST( + offchain_lookup_contract, monkeypatch, status_code_non_4xx_error, +) -> None: + normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower() + + # The next url in our test contract doesn't contain '{data}', triggering the POST request + # logic. The idea here is to return a bad status for the first url (GET) and a success + # status from the second call (POST) to test both that we move on to the next url with + # non 4xx status and that the POST logic is also working as expected. + mock_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501 + mocked_status_code=status_code_non_4xx_error, + mocked_json_data=WEB3PY_AS_HEXBYTES, + ) + mock_offchain_lookup_request_response( + monkeypatch, + http_method='POST', + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}.json', + mocked_status_code=200, + mocked_json_data=WEB3PY_AS_HEXBYTES, + sender=normalized_contract_address, + calldata=OFFCHAIN_LOOKUP_TEST_DATA, + ) + response = offchain_lookup_contract.caller.testOffchainLookup(OFFCHAIN_LOOKUP_TEST_DATA) + assert decode_abi(['string'], response)[0] == 'web3py' + + +@pytest.mark.parametrize('status_code_4xx_error', [400, 410, 450, 499]) +def test_eth_call_offchain_lookup_calls_raise_for_status_for_4xx_status_code( + offchain_lookup_contract, monkeypatch, status_code_4xx_error, +) -> None: + normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower() + mock_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501 + mocked_status_code=status_code_4xx_error, + mocked_json_data=WEB3PY_AS_HEXBYTES, + ) + with pytest.raises(Exception, match="called raise_for_status\\(\\)"): + offchain_lookup_contract.caller.testOffchainLookup(OFFCHAIN_LOOKUP_TEST_DATA) + + +def test_offchain_lookup_raises_on_continuous_redirect( + offchain_lookup_contract, monkeypatch, +): + normalized_address = to_hex_if_bytes(offchain_lookup_contract.address) + mock_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_address}/0x.json', + ) + with pytest.raises(TooManyRequests, match="Too many CCIP read redirects"): + offchain_lookup_contract.caller.continuousOffchainLookup() diff --git a/tests/ens/test_offchain_resolution.py b/tests/ens/test_offchain_resolution.py index 64dcace73f..ef929f55b8 100644 --- a/tests/ens/test_offchain_resolution.py +++ b/tests/ens/test_offchain_resolution.py @@ -46,6 +46,8 @@ class MockHttpSuccessResponse: + status_code = 200 + def __init__(self, request_type, *args, **_kwargs): # validate the expected urls if request_type == 'get': @@ -54,25 +56,23 @@ def __init__(self, request_type, *args, **_kwargs): assert args[1] == EXPECTED_POST_URL @staticmethod - def raise_for_status(): - pass + def raise_for_status(): pass # noqa: E704 @staticmethod - def json(): - return {'data': OFFCHAIN_RESOLVER_DATA} + def json(): return {'data': OFFCHAIN_RESOLVER_DATA} # noqa: E704 class MockHttpBadFormatResponse: + status_code = 200 + def __init__(self, *args): assert args[1] == EXPECTED_GET_URL @staticmethod - def raise_for_status(): - pass + def raise_for_status(): pass # noqa: E704 @staticmethod - def json(): - return {'not_data': OFFCHAIN_RESOLVER_DATA} + def json(): return {'not_data': OFFCHAIN_RESOLVER_DATA} # noqa: E704 def test_offchain_resolution_with_get_request(ens, monkeypatch): diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index c9671aa285..241eec652a 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -9,6 +9,10 @@ MATH_ABI, MATH_BYTECODE, ) +from web3._utils.module_testing.offchain_lookup_contract import ( + OFFCHAIN_LOOKUP_ABI, + OFFCHAIN_LOOKUP_BYTECODE, +) from web3._utils.module_testing.revert_contract import ( _REVERT_CONTRACT_ABI, REVERT_CONTRACT_BYTECODE, @@ -37,6 +41,14 @@ def revert_contract_factory(w3): return contract_factory +@pytest.fixture(scope="module") +def offchain_lookup_contract_factory(w3): + contract_factory = w3.eth.contract( + abi=OFFCHAIN_LOOKUP_ABI, bytecode=OFFCHAIN_LOOKUP_BYTECODE + ) + return contract_factory + + @pytest.fixture(scope="module") def event_loop(request): loop = asyncio.get_event_loop_policy().new_event_loop() diff --git a/tests/integration/generate_fixtures/go_ethereum.py b/tests/integration/generate_fixtures/go_ethereum.py index eadbfb00bc..cbe431c8a9 100644 --- a/tests/integration/generate_fixtures/go_ethereum.py +++ b/tests/integration/generate_fixtures/go_ethereum.py @@ -3,6 +3,7 @@ import os import pprint import shutil +import socket import subprocess import sys import time @@ -21,9 +22,6 @@ ) import common -from tests.utils import ( - get_open_port, -) from web3 import Web3 from web3._utils.module_testing.emitter_contract import ( CONTRACT_EMITTER_ABI, @@ -34,12 +32,24 @@ MATH_ABI, MATH_BYTECODE, ) +from web3._utils.module_testing.offchain_lookup_contract import ( + OFFCHAIN_LOOKUP_ABI, + OFFCHAIN_LOOKUP_BYTECODE, +) from web3._utils.module_testing.revert_contract import ( _REVERT_CONTRACT_ABI, REVERT_CONTRACT_BYTECODE, ) +def get_open_port(): + sock = socket.socket() + sock.bind(('127.0.0.1', 0)) + port = sock.getsockname()[1] + sock.close() + return str(port) + + @contextlib.contextmanager def graceful_kill_on_exit(proc): try: @@ -258,6 +268,18 @@ def setup_chain_state(w3): block_hash_revert_no_msg = w3.eth.get_block(txn_receipt_revert_with_no_msg['blockHash']) print('BLOCK_HASH_REVERT_NO_MSG:', block_hash_revert_no_msg['hash']) + # + # Offchain Lookup Contract + # + offchain_lookup_factory = w3.eth.contract( + abi=OFFCHAIN_LOOKUP_ABI, + bytecode=OFFCHAIN_LOOKUP_BYTECODE, + ) + offchain_lookup_deploy_receipt = common.deploy_contract( + w3, 'offchain_lookup', offchain_lookup_factory + ) + assert is_dict(offchain_lookup_deploy_receipt) + # # Empty Block # @@ -290,6 +312,7 @@ def setup_chain_state(w3): 'math_address': math_deploy_receipt['contractAddress'], 'emitter_deploy_txn_hash': emitter_deploy_receipt['transactionHash'], 'emitter_address': emitter_deploy_receipt['contractAddress'], + 'offchain_lookup_address': offchain_lookup_deploy_receipt['contractAddress'], 'txn_hash_with_log': txn_hash_with_log, 'block_hash_with_log': block_with_log['hash'], 'empty_block_hash': empty_block['hash'], diff --git a/tests/integration/geth-1.10.17-fixture.zip b/tests/integration/geth-1.10.17-fixture.zip index cf4a8ab6dfca5a7edde3dd1995177b5f54f3df62..0cd0e7d5c0baf3e0419b70641dcf1502508e2eaa 100644 GIT binary patch delta 42342 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

<9`Jm0pFf1&xc?< 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 ATL1t6 delta 35927 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 GmG=Qbv5j;9 diff --git a/tests/integration/go_ethereum/conftest.py b/tests/integration/go_ethereum/conftest.py index c4ce4bf79a..d861e47dd1 100644 --- a/tests/integration/go_ethereum/conftest.py +++ b/tests/integration/go_ethereum/conftest.py @@ -236,3 +236,8 @@ def block_hash_revert_with_msg(geth_fixture_data): @pytest.fixture(scope="module") def revert_contract(revert_contract_factory, geth_fixture_data): return revert_contract_factory(address=geth_fixture_data['revert_address']) + + +@pytest.fixture(scope="module") +def offchain_lookup_contract(offchain_lookup_contract_factory, geth_fixture_data): + return offchain_lookup_contract_factory(address=geth_fixture_data['offchain_lookup_address']) diff --git a/tests/integration/test_ethereum_tester.py b/tests/integration/test_ethereum_tester.py index c688fa1f14..d3390a1bdb 100644 --- a/tests/integration/test_ethereum_tester.py +++ b/tests/integration/test_ethereum_tester.py @@ -159,6 +159,21 @@ def revert_contract(w3, revert_contract_factory, revert_contract_deploy_txn_hash return revert_contract_factory(contract_address) +# +# Offchain Lookup Contract Setup +# +@pytest.fixture(scope="module") +def offchain_lookup_contract(w3, offchain_lookup_contract_factory): + deploy_txn_hash = offchain_lookup_contract_factory.constructor().transact( + {'from': w3.eth.coinbase} + ) + deploy_receipt = w3.eth.wait_for_transaction_receipt(deploy_txn_hash) + assert is_dict(deploy_receipt) + contract_address = deploy_receipt['contractAddress'] + assert is_checksum_address(contract_address) + return offchain_lookup_contract_factory(contract_address) + + UNLOCKABLE_PRIVATE_KEY = '0x392f63a79b1ff8774845f3fa69de4a13800a59e7083f5187f1558f0797ad0f01' diff --git a/tests/core/contracts/contract_sources/Emitter.sol b/web3/_utils/contract_sources/Emitter.sol similarity index 100% rename from tests/core/contracts/contract_sources/Emitter.sol rename to web3/_utils/contract_sources/Emitter.sol diff --git a/tests/core/contracts/contract_sources/Emitter_old.sol b/web3/_utils/contract_sources/Emitter_old.sol similarity index 100% rename from tests/core/contracts/contract_sources/Emitter_old.sol rename to web3/_utils/contract_sources/Emitter_old.sol diff --git a/tests/ens/test_contracts/ExtendedResolver.sol b/web3/_utils/contract_sources/ExtendedResolver.sol similarity index 100% rename from tests/ens/test_contracts/ExtendedResolver.sol rename to web3/_utils/contract_sources/ExtendedResolver.sol diff --git a/web3/_utils/contract_sources/OffchainLookup.sol b/web3/_utils/contract_sources/OffchainLookup.sol new file mode 100644 index 0000000000..3d2a1649d7 --- /dev/null +++ b/web3/_utils/contract_sources/OffchainLookup.sol @@ -0,0 +1,54 @@ +// This contract is meant to test CCIP Read / Offchain Lookup functionality as part of EIP-3668. Multiple functions +// may be added here for testing and the contract can be recompiled for `test_offchain_lookup.py` and other tests. + +pragma solidity ^0.8.13; + +contract OffchainLookupTests { + string[] urls = ["https://web3.py/gateway/{sender}/{data}.json", "https://web3.py/gateway/{sender}.json"]; + + error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData); + + // This function is meant to test the offchain lookup functionality specified in EIP-3668. + function testOffchainLookup(bytes calldata specifiedDataFromTest) external returns(bytes memory) { + // assert that the test specifies "offchain lookup test" to start the test + string memory dataFromTestAsString = abi.decode(specifiedDataFromTest, (string)); + require(keccak256(abi.encodePacked(dataFromTestAsString)) == keccak256("test offchain lookup"), "test data validation failed."); + + revert OffchainLookup( + address(this), + urls, + specifiedDataFromTest, + this.testOffchainLookupWithProof.selector, + specifiedDataFromTest + ); + } + + function testOffchainLookupWithProof(bytes calldata result, bytes calldata extraData) external returns(bytes memory) { + // assert the result came from the mocked response from our tests... must mock in tests with the appropriate + // value and validate the assertion here. + string memory resultAsString = abi.decode(result, (string)); + require(keccak256(abi.encodePacked(resultAsString)) == keccak256("web3py"), "http request result validation failed."); + + // assert this `extraData` value is the same test-provided value from the original revert at `testOffchainLookup()` + string memory extraDataAsString = abi.decode(extraData, (string)); + require(keccak256(abi.encodePacked(extraDataAsString)) == keccak256("test offchain lookup"), "extraData validation failed."); + + return result; + } + + // This function is meant to test that continuous OffchainLookup reverts raise an exception after too many + // redirects. This example technically breaks the flow described in EIP-3668 but this is solely meant to trigger + // continuous OffchainLookup reverts and test that we catch this sort of activity and stop it. Currently this limit + // is set to 4 redirects. + function continuousOffchainLookup() external returns(bytes memory) { + bytes memory _callData; + + revert OffchainLookup( + address(this), + urls, + _callData, + this.continuousOffchainLookup.selector, + _callData + ); + } +} diff --git a/tests/ens/test_contracts/OffchainResolver.sol b/web3/_utils/contract_sources/OffchainResolver.sol similarity index 96% rename from tests/ens/test_contracts/OffchainResolver.sol rename to web3/_utils/contract_sources/OffchainResolver.sol index 7c2ff39332..863beffdfa 100644 --- a/tests/ens/test_contracts/OffchainResolver.sol +++ b/web3/_utils/contract_sources/OffchainResolver.sol @@ -1,7 +1,9 @@ -// This contract is a much simpler version of the contract set up on mainnet for 'offchainexample.eth', deployed at -// the address 0xC1735677a60884ABbCF72295E88d47764BeDa282. We get rid of the 'expires' time constraint on the -// SignatureVerifier.verify() check so that we can use an actual 'data' payload that comes back from resolving via -// the OffchainResolver for 'offchainexample.eth' on mainnet. A bit hacky but it works for the purpose of testing :) +/** + * This contract is a much simpler version of the contract set up on mainnet for 'offchainexample.eth', deployed at + * the address 0xC1735677a60884ABbCF72295E88d47764BeDa282. We get rid of the 'expires' time constraint on the + * SignatureVerifier.verify() check so that we can use an actual 'data' payload that comes back from resolving via + * the OffchainResolver for 'offchainexample.eth' on mainnet. A bit hacky but it works for the purpose of testing :) + */ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Strings.sol) diff --git a/tests/ens/test_contracts/SimpleResolver.sol b/web3/_utils/contract_sources/SimpleResolver.sol similarity index 100% rename from tests/ens/test_contracts/SimpleResolver.sol rename to web3/_utils/contract_sources/SimpleResolver.sol diff --git a/web3/_utils/module_testing/emitter_contract_old.py b/web3/_utils/module_testing/emitter_contract_old.py index 789699d8eb..1cacae0f36 100644 --- a/web3/_utils/module_testing/emitter_contract_old.py +++ b/web3/_utils/module_testing/emitter_contract_old.py @@ -4,6 +4,8 @@ # compiled with `0.8.11`. # See: https://github.com/ethereum/web3.py/issues/2301 +# contract source at web3/_utils/contract_sources/Emitter_old.sol + CONTRACT_EMITTER_CODE_OLD = ( "608060405234801561001057600080fd5b50610aed806100206000396000f300608060405260043" "6106100ae5763ffffffff7c01000000000000000000000000000000000000000000000000000000" diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py index 5f0ec7e88b..8fec309cc5 100644 --- a/web3/_utils/module_testing/eth_module.py +++ b/web3/_utils/module_testing/eth_module.py @@ -1,11 +1,9 @@ import json import math import pytest -import time from typing import ( TYPE_CHECKING, Callable, - Sequence, Union, cast, ) @@ -42,6 +40,15 @@ from web3._utils.method_formatters import ( to_hex_if_integer, ) +from web3._utils.module_testing.utils import ( + assert_contains_log, + mine_pending_block, + mock_async_offchain_lookup_request_response, + mock_offchain_lookup_request_response, +) +from web3._utils.type_conversion_utils import ( + to_hex_if_bytes, +) from web3.exceptions import ( BlockNotFound, ContractLogicError, @@ -49,6 +56,7 @@ InvalidTransaction, NameNotFound, TimeExhausted, + TooManyRequests, TransactionNotFound, TransactionTypeMismatch, ValidationError, @@ -74,37 +82,15 @@ UNKNOWN_ADDRESS = ChecksumAddress(HexAddress(HexStr('0xdEADBEeF00000000000000000000000000000000'))) UNKNOWN_HASH = HexStr('0xdeadbeef00000000000000000000000000000000000000000000000000000000') +# "test offchain lookup" as an abi-encoded string +OFFCHAIN_LOOKUP_TEST_DATA = '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501 +# "web3py" as an abi-encoded string +WEB3PY_AS_HEXBYTES = '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000067765623370790000000000000000000000000000000000000000000000000000' # noqa: E501 if TYPE_CHECKING: from web3 import Web3 # noqa: F401 from web3.contract import Contract # noqa: F401 - - -def mine_pending_block(w3: "Web3") -> None: - timeout = 10 - - w3.geth.miner.start() # type: ignore - start = time.time() - while time.time() < start + timeout: - if len(w3.eth.get_block('pending')['transactions']) == 0: - break - w3.geth.miner.stop() # type: ignore - - -def _assert_contains_log( - result: Sequence[LogReceipt], - block_with_txn_with_log: BlockData, - emitter_contract_address: ChecksumAddress, - txn_hash_with_log: HexStr, -) -> None: - assert len(result) == 1 - log_entry = result[0] - assert log_entry['blockNumber'] == block_with_txn_with_log['number'] - assert log_entry['blockHash'] == block_with_txn_with_log['hash'] - assert log_entry['logIndex'] == 0 - assert is_same_address(log_entry['address'], emitter_contract_address) - assert log_entry['transactionIndex'] == 0 - assert log_entry['transactionHash'] == HexBytes(txn_hash_with_log) + from _pytest.monkeypatch import MonkeyPatch # noqa: F401 class AsyncEthModuleTest: @@ -767,6 +753,132 @@ async def test_eth_call_revert_without_msg( ) await async_w3.eth.call(txn_params) # type: ignore + @pytest.mark.asyncio + async def test_eth_call_offchain_lookup( + self, + async_w3: "Web3", + offchain_lookup_contract: "Contract", + unlocked_account: ChecksumAddress, + monkeypatch: "MonkeyPatch", + ) -> None: + normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower() + mock_async_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501 + mocked_json_data=WEB3PY_AS_HEXBYTES, + ) + # TODO: change to contract call when async Contract is supported + tx = { + 'to': offchain_lookup_contract.address, + 'data': '0x6337ed58000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501 + } + async_w3.provider.ccip_read_enabled = True + response = await async_w3.eth.call(tx) # type: ignore + response_as_bytes = async_w3.codec.decode_abi(['bytes'], response)[0] + decoded_as_string = async_w3.codec.decode_abi(['string'], response_as_bytes)[0] + assert decoded_as_string == 'web3py' + + @pytest.mark.asyncio + @pytest.mark.parametrize('status_code_non_4xx_error', [100, 300, 500, 600]) + async def test_eth_call_offchain_lookup_tries_next_url_for_non_4xx_error_status_and_tests_POST( + self, + async_w3: "Web3", + offchain_lookup_contract: "Contract", + unlocked_account: ChecksumAddress, + monkeypatch: "MonkeyPatch", + status_code_non_4xx_error: int, + ) -> None: + normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower() + + # The next url in our test contract doesn't contain '{data}', triggering the POST request + # logic. The idea here is to return a bad status for the first url (GET) and a success + # status for the second call (POST) to test both that we move on to the next url with + # non-4xx status and that the POST logic is also working as expected. + mock_async_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501 + mocked_status_code=status_code_non_4xx_error, + mocked_json_data=WEB3PY_AS_HEXBYTES, + ) + mock_async_offchain_lookup_request_response( + monkeypatch, + http_method='POST', + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}.json', + mocked_status_code=200, + mocked_json_data=WEB3PY_AS_HEXBYTES, + sender=normalized_contract_address, + calldata=OFFCHAIN_LOOKUP_TEST_DATA, + ) + # TODO: change to contract call when async Contract is supported + tx = { + 'to': offchain_lookup_contract.address, + 'data': '0x6337ed58000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501 + } + async_w3.provider.ccip_read_enabled = True + response = await async_w3.eth.call(tx) # type: ignore + response_as_bytes = async_w3.codec.decode_abi(['bytes'], response)[0] + decoded_as_string = async_w3.codec.decode_abi(['string'], response_as_bytes)[0] + assert decoded_as_string == 'web3py' + + @pytest.mark.asyncio + @pytest.mark.parametrize('status_code_4xx_error', [400, 410, 450, 499]) + async def test_eth_call_offchain_lookup_calls_raise_for_status_for_4xx_status_code( + self, + async_w3: "Web3", + offchain_lookup_contract: "Contract", + unlocked_account: ChecksumAddress, + monkeypatch: "MonkeyPatch", + status_code_4xx_error: int, + ) -> None: + normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower() + mock_async_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501 + mocked_status_code=status_code_4xx_error, + mocked_json_data=WEB3PY_AS_HEXBYTES, + ) + with pytest.raises(Exception, match="called raise_for_status\\(\\)"): + # TODO: change to contract call when async Contract is supported + tx = { + 'to': offchain_lookup_contract.address, + 'data': '0x6337ed58000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501 + } + async_w3.provider.ccip_read_enabled = True + await async_w3.eth.call(tx) # type: ignore + + @pytest.mark.asyncio + async def test_eth_call_offchain_lookup_raises_when_all_supplied_urls_fail( + self, async_w3: "Web3", offchain_lookup_contract: "Contract", + ) -> None: + # GET and POST requests should fail since responses are not mocked + with pytest.raises(Exception, match="Offchain lookup failed for supplied urls"): + # TODO: change to contract call when async Contract is supported + tx = { + 'to': offchain_lookup_contract.address, + 'data': '0x6337ed58000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001474657374206f6666636861696e206c6f6f6b7570000000000000000000000000' # noqa: E501 + } + async_w3.provider.ccip_read_enabled = True + await async_w3.eth.call(tx) # type: ignore + + @pytest.mark.asyncio + async def test_eth_call_continuous_offchain_lookup_raises_with_too_many_requests( + self, + async_w3: "Web3", + offchain_lookup_contract: "Contract", + unlocked_account: ChecksumAddress, + monkeypatch: "MonkeyPatch", + ) -> None: + normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower() + mock_async_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/0x.json', + ) + with pytest.raises(TooManyRequests, match="Too many CCIP read redirects"): + # TODO: change to contract call when async Contract is supported + tx = {'to': offchain_lookup_contract.address, 'data': '0x09a3c01b'} + async_w3.provider.ccip_read_enabled = True + await async_w3.eth.call(tx) # type: ignore + @pytest.mark.asyncio async def test_async_eth_hashrate( self, @@ -990,7 +1102,7 @@ async def test_async_eth_get_logs_with_logs( "toBlock": block_with_txn_with_log['number'], } result = await async_w3.eth.get_logs(filter_params) # type: ignore - _assert_contains_log( + assert_contains_log( result, block_with_txn_with_log, emitter_contract_address, @@ -1002,7 +1114,7 @@ async def test_async_eth_get_logs_with_logs( "fromBlock": BlockNumber(0), } result = await async_w3.eth.get_logs(filter_params) # type: ignore - _assert_contains_log( + assert_contains_log( result, block_with_txn_with_log, emitter_contract_address, @@ -1017,7 +1129,7 @@ async def test_async_eth_get_logs_with_logs( "address": emitter_contract_address, } result = await async_w3.eth.get_logs(filter_params) # type: ignore - _assert_contains_log( + assert_contains_log( result, block_with_txn_with_log, emitter_contract_address, @@ -1043,7 +1155,7 @@ async def test_async_eth_get_logs_with_logs_topic_args( } result = await async_w3.eth.get_logs(filter_params) # type: ignore - _assert_contains_log( + assert_contains_log( result, block_with_txn_with_log, emitter_contract_address, @@ -1058,7 +1170,7 @@ async def test_async_eth_get_logs_with_logs_topic_args( None], } result = await async_w3.eth.get_logs(filter_params) # type: ignore - _assert_contains_log( + assert_contains_log( result, block_with_txn_with_log, emitter_contract_address, @@ -2577,6 +2689,104 @@ def test_eth_call_revert_without_msg( ) w3.eth.call(txn_params) + def test_eth_call_offchain_lookup( + self, + w3: "Web3", + offchain_lookup_contract: "Contract", + unlocked_account: ChecksumAddress, + monkeypatch: "MonkeyPatch", + ) -> None: + normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower() + mock_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501 + mocked_json_data=WEB3PY_AS_HEXBYTES, + ) + response = offchain_lookup_contract.functions.testOffchainLookup( + OFFCHAIN_LOOKUP_TEST_DATA + ).call(ccip_read_enabled=True) + assert w3.codec.decode_abi(['string'], response)[0] == 'web3py' + + @pytest.mark.parametrize('status_code_non_4xx_error', [100, 300, 500, 600]) + def test_eth_call_offchain_lookup_tries_next_url_for_non_4xx_error_status_and_tests_POST( + self, + w3: "Web3", + offchain_lookup_contract: "Contract", + unlocked_account: ChecksumAddress, + monkeypatch: "MonkeyPatch", + status_code_non_4xx_error: int, + ) -> None: + normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower() + + # The next url in our test contract doesn't contain '{data}', triggering the POST request + # logic. The idea here is to return a bad status for the first url (GET) and a success + # status from the second call (POST) to test both that we move on to the next url with + # non 4xx status and that the POST logic is also working as expected. + mock_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501 + mocked_status_code=status_code_non_4xx_error, + mocked_json_data=WEB3PY_AS_HEXBYTES, + ) + mock_offchain_lookup_request_response( + monkeypatch, + http_method='POST', + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}.json', + mocked_status_code=200, + mocked_json_data=WEB3PY_AS_HEXBYTES, + sender=normalized_contract_address, + calldata=OFFCHAIN_LOOKUP_TEST_DATA, + ) + response = offchain_lookup_contract.functions.testOffchainLookup( + OFFCHAIN_LOOKUP_TEST_DATA + ).call(ccip_read_enabled=True) + assert w3.codec.decode_abi(['string'], response)[0] == 'web3py' + + @pytest.mark.parametrize('status_code_4xx_error', [400, 410, 450, 499]) + def test_eth_call_offchain_lookup_calls_raise_for_status_for_4xx_status_code( + self, + w3: "Web3", + offchain_lookup_contract: "Contract", + unlocked_account: ChecksumAddress, + monkeypatch: "MonkeyPatch", + status_code_4xx_error: int, + ) -> None: + normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower() + mock_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/{OFFCHAIN_LOOKUP_TEST_DATA}.json', # noqa: E501 + mocked_status_code=status_code_4xx_error, + mocked_json_data=WEB3PY_AS_HEXBYTES, + ) + with pytest.raises(Exception, match="called raise_for_status\\(\\)"): + offchain_lookup_contract.functions.testOffchainLookup( + OFFCHAIN_LOOKUP_TEST_DATA + ).call(ccip_read_enabled=True) + + def test_eth_call_offchain_lookup_raises_when_all_supplied_urls_fail( + self, w3: "Web3", offchain_lookup_contract: "Contract", + ) -> None: + # GET and POST requests should fail since responses are not mocked + with pytest.raises(Exception, match="Offchain lookup failed for supplied urls"): + offchain_lookup_contract.functions.testOffchainLookup( + OFFCHAIN_LOOKUP_TEST_DATA + ).call(ccip_read_enabled=True) + + def test_eth_call_continuous_offchain_lookup_raises_with_too_many_requests( + self, + w3: "Web3", + offchain_lookup_contract: "Contract", + unlocked_account: ChecksumAddress, + monkeypatch: "MonkeyPatch", + ) -> None: + normalized_contract_address = to_hex_if_bytes(offchain_lookup_contract.address).lower() + mock_offchain_lookup_request_response( + monkeypatch, + mocked_request_url=f'https://web3.py/gateway/{normalized_contract_address}/0x.json', + ) + with pytest.raises(TooManyRequests, match="Too many CCIP read redirects"): + offchain_lookup_contract.caller(ccip_read_enabled=True).continuousOffchainLookup() + def test_eth_estimate_gas_revert_with_msg( self, w3: "Web3", @@ -3036,7 +3246,7 @@ def test_eth_get_logs_with_logs( "toBlock": block_with_txn_with_log['number'], } result = w3.eth.get_logs(filter_params) - _assert_contains_log( + assert_contains_log( result, block_with_txn_with_log, emitter_contract_address, @@ -3048,7 +3258,7 @@ def test_eth_get_logs_with_logs( "fromBlock": BlockNumber(0), } result = w3.eth.get_logs(filter_params) - _assert_contains_log( + assert_contains_log( result, block_with_txn_with_log, emitter_contract_address, @@ -3081,7 +3291,7 @@ def test_eth_get_logs_with_logs_topic_args( } result = w3.eth.get_logs(filter_params) - _assert_contains_log( + assert_contains_log( result, block_with_txn_with_log, emitter_contract_address, @@ -3096,7 +3306,7 @@ def test_eth_get_logs_with_logs_topic_args( None], } result = w3.eth.get_logs(filter_params) - _assert_contains_log( + assert_contains_log( result, block_with_txn_with_log, emitter_contract_address, diff --git a/web3/_utils/module_testing/offchain_lookup_contract.py b/web3/_utils/module_testing/offchain_lookup_contract.py new file mode 100644 index 0000000000..ee4240b8bd --- /dev/null +++ b/web3/_utils/module_testing/offchain_lookup_contract.py @@ -0,0 +1,104 @@ +import json + +# contract source at .contract_sources/OffchainLookup.sol +OFFCHAIN_LOOKUP_BYTECODE = "608060405260405180604001604052806040518060600160405280602c815260200162000ec9602c9139815260200160405180606001604052806025815260200162000ef56025913981525060009060026200005d92919062000072565b503480156200006b57600080fd5b506200025b565b828054828255906000526020600020908101928215620000c6579160200282015b82811115620000c5578251829080519060200190620000b4929190620000d9565b509160200191906001019062000093565b5b509050620000d591906200016a565b5090565b828054620000e79062000226565b90600052602060002090601f0160209004810192826200010b576000855562000157565b82601f106200012657805160ff191683800117855562000157565b8280016001018555821562000157579182015b828111156200015657825182559160200191906001019062000139565b5b50905062000166919062000192565b5090565b5b808211156200018e5760008181620001849190620001b1565b506001016200016b565b5090565b5b80821115620001ad57600081600090555060010162000193565b5090565b508054620001bf9062000226565b6000825580601f10620001d35750620001f4565b601f016020900490600052602060002090810190620001f3919062000192565b5b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200023f57607f821691505b602082108103620002555762000254620001f7565b5b50919050565b610c5e806200026b6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806309a3c01b146100465780636337ed5814610064578063da96d05a14610094575b600080fd5b61004e6100c4565b60405161005b9190610424565b60405180910390f35b61007e600480360381019061007991906104bf565b610114565b60405161008b9190610424565b60405180910390f35b6100ae60048036038101906100a9919061050c565b610202565b6040516100bb9190610424565b60405180910390f35b606080306000826309a3c01b60e01b846040517f556f183000000000000000000000000000000000000000000000000000000000815260040161010b9594939291906107d5565b60405180910390fd5b606060008383810190610127919061096d565b90507fd9bdd1345ca2a00d0c1413137c1b2b1d0a35e5b0e11508f3b3eff856286af0758160405160200161015b91906109fd565b60405160208183030381529060405280519060200120146101b1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101a890610a71565b60405180910390fd5b306000858563da96d05a60e01b88886040517f556f18300000000000000000000000000000000000000000000000000000000081526004016101f99796959493929190610abe565b60405180910390fd5b606060008585810190610215919061096d565b90507faed76f463930323372899e36460e078e5292aac45f645bbe567be6fca83ede108160405160200161024991906109fd565b604051602081830303815290604052805190602001201461029f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161029690610b9c565b60405180910390fd5b600084848101906102b0919061096d565b90507fd9bdd1345ca2a00d0c1413137c1b2b1d0a35e5b0e11508f3b3eff856286af075816040516020016102e491906109fd565b604051602081830303815290604052805190602001201461033a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161033190610c08565b60405180910390fd5b86868080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505092505050949350505050565b600081519050919050565b600082825260208201905092915050565b60005b838110156103c55780820151818401526020810190506103aa565b838111156103d4576000848401525b50505050565b6000601f19601f8301169050919050565b60006103f68261038b565b6104008185610396565b93506104108185602086016103a7565b610419816103da565b840191505092915050565b6000602082019050818103600083015261043e81846103eb565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f84011261047f5761047e61045a565b5b8235905067ffffffffffffffff81111561049c5761049b61045f565b5b6020830191508360018202830111156104b8576104b7610464565b5b9250929050565b600080602083850312156104d6576104d5610450565b5b600083013567ffffffffffffffff8111156104f4576104f3610455565b5b61050085828601610469565b92509250509250929050565b6000806000806040858703121561052657610525610450565b5b600085013567ffffffffffffffff81111561054457610543610455565b5b61055087828801610469565b9450945050602085013567ffffffffffffffff81111561057357610572610455565b5b61057f87828801610469565b925092505092959194509250565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006105b88261058d565b9050919050565b6105c8816105ad565b82525050565b600081549050919050565b600082825260208201905092915050565b60008190508160005260206000209050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061064657607f821691505b602082108103610659576106586105ff565b5b50919050565b600082825260208201905092915050565b60008190508160005260206000209050919050565b600081546106928161062e565b61069c818661065f565b945060018216600081146106b757600181146106c9576106fc565b60ff19831686526020860193506106fc565b6106d285610670565b60005b838110156106f4578154818901526001820191506020810190506106d5565b808801955050505b50505092915050565b60006107118383610685565b905092915050565b6000600182019050919050565b6000610731826105ce565b61073b81856105d9565b93508360208202850161074d856105ea565b8060005b85811015610788578484038952816107698582610705565b945061077483610719565b925060208a01995050600181019050610751565b50829750879550505050505092915050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6107cf8161079a565b82525050565b600060a0820190506107ea60008301886105bf565b81810360208301526107fc8187610726565b9050818103604083015261081081866103eb565b905061081f60608301856107c6565b818103608083015261083181846103eb565b90509695505050505050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61087a826103da565b810181811067ffffffffffffffff8211171561089957610898610842565b5b80604052505050565b60006108ac610446565b90506108b88282610871565b919050565b600067ffffffffffffffff8211156108d8576108d7610842565b5b6108e1826103da565b9050602081019050919050565b82818337600083830152505050565b600061091061090b846108bd565b6108a2565b90508281526020810184848401111561092c5761092b61083d565b5b6109378482856108ee565b509392505050565b600082601f8301126109545761095361045a565b5b81356109648482602086016108fd565b91505092915050565b60006020828403121561098357610982610450565b5b600082013567ffffffffffffffff8111156109a1576109a0610455565b5b6109ad8482850161093f565b91505092915050565b600081519050919050565b600081905092915050565b60006109d7826109b6565b6109e181856109c1565b93506109f18185602086016103a7565b80840191505092915050565b6000610a0982846109cc565b915081905092915050565b600082825260208201905092915050565b7f7465737420646174612076616c69646174696f6e206661696c65642e00000000600082015250565b6000610a5b601c83610a14565b9150610a6682610a25565b602082019050919050565b60006020820190508181036000830152610a8a81610a4e565b9050919050565b6000610a9d8385610396565b9350610aaa8385846108ee565b610ab3836103da565b840190509392505050565b600060a082019050610ad3600083018a6105bf565b8181036020830152610ae58189610726565b90508181036040830152610afa818789610a91565b9050610b0960608301866107c6565b8181036080830152610b1c818486610a91565b905098975050505050505050565b7f68747470207265717565737420726573756c742076616c69646174696f6e206660008201527f61696c65642e0000000000000000000000000000000000000000000000000000602082015250565b6000610b86602683610a14565b9150610b9182610b2a565b604082019050919050565b60006020820190508181036000830152610bb581610b79565b9050919050565b7f6578747261446174612076616c69646174696f6e206661696c65642e00000000600082015250565b6000610bf2601c83610a14565b9150610bfd82610bbc565b602082019050919050565b60006020820190508181036000830152610c2181610be5565b905091905056fea2646970667358221220528c32029f8724a4e2b6a2a469880824c952eae5971f18628559629192c102b164736f6c634300080d003368747470733a2f2f776562332e70792f676174657761792f7b73656e6465727d2f7b646174617d2e6a736f6e68747470733a2f2f776562332e70792f676174657761792f7b73656e6465727d2e6a736f6e" # noqa: E501 +OFFCHAIN_LOOKUP_BYTECODE_RUNTIME = "608060405234801561001057600080fd5b50600436106100415760003560e01c806309a3c01b146100465780636337ed5814610064578063da96d05a14610094575b600080fd5b61004e6100c4565b60405161005b9190610424565b60405180910390f35b61007e600480360381019061007991906104bf565b610114565b60405161008b9190610424565b60405180910390f35b6100ae60048036038101906100a9919061050c565b610202565b6040516100bb9190610424565b60405180910390f35b606080306000826309a3c01b60e01b846040517f556f183000000000000000000000000000000000000000000000000000000000815260040161010b9594939291906107d5565b60405180910390fd5b606060008383810190610127919061096d565b90507fd9bdd1345ca2a00d0c1413137c1b2b1d0a35e5b0e11508f3b3eff856286af0758160405160200161015b91906109fd565b60405160208183030381529060405280519060200120146101b1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101a890610a71565b60405180910390fd5b306000858563da96d05a60e01b88886040517f556f18300000000000000000000000000000000000000000000000000000000081526004016101f99796959493929190610abe565b60405180910390fd5b606060008585810190610215919061096d565b90507faed76f463930323372899e36460e078e5292aac45f645bbe567be6fca83ede108160405160200161024991906109fd565b604051602081830303815290604052805190602001201461029f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161029690610b9c565b60405180910390fd5b600084848101906102b0919061096d565b90507fd9bdd1345ca2a00d0c1413137c1b2b1d0a35e5b0e11508f3b3eff856286af075816040516020016102e491906109fd565b604051602081830303815290604052805190602001201461033a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161033190610c08565b60405180910390fd5b86868080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505092505050949350505050565b600081519050919050565b600082825260208201905092915050565b60005b838110156103c55780820151818401526020810190506103aa565b838111156103d4576000848401525b50505050565b6000601f19601f8301169050919050565b60006103f68261038b565b6104008185610396565b93506104108185602086016103a7565b610419816103da565b840191505092915050565b6000602082019050818103600083015261043e81846103eb565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f84011261047f5761047e61045a565b5b8235905067ffffffffffffffff81111561049c5761049b61045f565b5b6020830191508360018202830111156104b8576104b7610464565b5b9250929050565b600080602083850312156104d6576104d5610450565b5b600083013567ffffffffffffffff8111156104f4576104f3610455565b5b61050085828601610469565b92509250509250929050565b6000806000806040858703121561052657610525610450565b5b600085013567ffffffffffffffff81111561054457610543610455565b5b61055087828801610469565b9450945050602085013567ffffffffffffffff81111561057357610572610455565b5b61057f87828801610469565b925092505092959194509250565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006105b88261058d565b9050919050565b6105c8816105ad565b82525050565b600081549050919050565b600082825260208201905092915050565b60008190508160005260206000209050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061064657607f821691505b602082108103610659576106586105ff565b5b50919050565b600082825260208201905092915050565b60008190508160005260206000209050919050565b600081546106928161062e565b61069c818661065f565b945060018216600081146106b757600181146106c9576106fc565b60ff19831686526020860193506106fc565b6106d285610670565b60005b838110156106f4578154818901526001820191506020810190506106d5565b808801955050505b50505092915050565b60006107118383610685565b905092915050565b6000600182019050919050565b6000610731826105ce565b61073b81856105d9565b93508360208202850161074d856105ea565b8060005b85811015610788578484038952816107698582610705565b945061077483610719565b925060208a01995050600181019050610751565b50829750879550505050505092915050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6107cf8161079a565b82525050565b600060a0820190506107ea60008301886105bf565b81810360208301526107fc8187610726565b9050818103604083015261081081866103eb565b905061081f60608301856107c6565b818103608083015261083181846103eb565b90509695505050505050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61087a826103da565b810181811067ffffffffffffffff8211171561089957610898610842565b5b80604052505050565b60006108ac610446565b90506108b88282610871565b919050565b600067ffffffffffffffff8211156108d8576108d7610842565b5b6108e1826103da565b9050602081019050919050565b82818337600083830152505050565b600061091061090b846108bd565b6108a2565b90508281526020810184848401111561092c5761092b61083d565b5b6109378482856108ee565b509392505050565b600082601f8301126109545761095361045a565b5b81356109648482602086016108fd565b91505092915050565b60006020828403121561098357610982610450565b5b600082013567ffffffffffffffff8111156109a1576109a0610455565b5b6109ad8482850161093f565b91505092915050565b600081519050919050565b600081905092915050565b60006109d7826109b6565b6109e181856109c1565b93506109f18185602086016103a7565b80840191505092915050565b6000610a0982846109cc565b915081905092915050565b600082825260208201905092915050565b7f7465737420646174612076616c69646174696f6e206661696c65642e00000000600082015250565b6000610a5b601c83610a14565b9150610a6682610a25565b602082019050919050565b60006020820190508181036000830152610a8a81610a4e565b9050919050565b6000610a9d8385610396565b9350610aaa8385846108ee565b610ab3836103da565b840190509392505050565b600060a082019050610ad3600083018a6105bf565b8181036020830152610ae58189610726565b90508181036040830152610afa818789610a91565b9050610b0960608301866107c6565b8181036080830152610b1c818486610a91565b905098975050505050505050565b7f68747470207265717565737420726573756c742076616c69646174696f6e206660008201527f61696c65642e0000000000000000000000000000000000000000000000000000602082015250565b6000610b86602683610a14565b9150610b9182610b2a565b604082019050919050565b60006020820190508181036000830152610bb581610b79565b9050919050565b7f6578747261446174612076616c69646174696f6e206661696c65642e00000000600082015250565b6000610bf2601c83610a14565b9150610bfd82610bbc565b602082019050919050565b60006020820190508181036000830152610c2181610be5565b905091905056fea2646970667358221220528c32029f8724a4e2b6a2a469880824c952eae5971f18628559629192c102b164736f6c634300080d0033" # noqa: E501 +OFFCHAIN_LOOKUP_ABI = json.loads('''[ + { + "inputs": [ + { + "internalType": "address", + "name": "sender", "type": "address" + }, + { + "internalType": "string[]", + "name": "urls", + "type": "string[]" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes"}, + { + "internalType": "bytes4", + "name": "callbackFunction", + "type":"bytes4" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "OffchainLookup", + "type": "error" + }, + { + "inputs": [], + "name": "continuousOffchainLookup", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type":"bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "testOffchainLookupData", + "outputs": [ + { + "internalType": "bytes", + "name": "", "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "specifiedDataFromTest", + "type": "bytes" + } + ], + "name": "testOffchainLookup", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "testOffchainLookupWithProof", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +]''') diff --git a/web3/_utils/module_testing/utils.py b/web3/_utils/module_testing/utils.py new file mode 100644 index 0000000000..bda4197cf6 --- /dev/null +++ b/web3/_utils/module_testing/utils.py @@ -0,0 +1,158 @@ +import time +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Generator, + Sequence, + Union, +) + +from aiohttp import ( + ClientResponse, + ClientSession, + ClientTimeout, +) +from eth_typing import ( + ChecksumAddress, + HexStr, +) +from eth_utils import ( + is_same_address, +) +from hexbytes import ( + HexBytes, +) +import requests +from requests import ( + Response, +) + +from web3._utils.compat import ( + Literal, +) +from web3.types import ( + BlockData, + LogReceipt, +) + +if TYPE_CHECKING: + from web3 import Web3 # noqa: F401 + from _pytest.monkeypatch import MonkeyPatch # noqa: F401 + + +def mine_pending_block(w3: "Web3") -> None: + timeout = 10 + + w3.geth.miner.start() # type: ignore + start = time.time() + while time.time() < start + timeout: + if len(w3.eth.get_block('pending')['transactions']) == 0: + break + w3.geth.miner.stop() # type: ignore + + +def assert_contains_log( + result: Sequence[LogReceipt], + block_with_txn_with_log: BlockData, + emitter_contract_address: ChecksumAddress, + txn_hash_with_log: HexStr, +) -> None: + assert len(result) == 1 + log_entry = result[0] + assert log_entry['blockNumber'] == block_with_txn_with_log['number'] + assert log_entry['blockHash'] == block_with_txn_with_log['hash'] + assert log_entry['logIndex'] == 0 + assert is_same_address(log_entry['address'], emitter_contract_address) + assert log_entry['transactionIndex'] == 0 + assert log_entry['transactionHash'] == HexBytes(txn_hash_with_log) + + +def mock_offchain_lookup_request_response( + monkeypatch: "MonkeyPatch", + http_method: Literal['GET', 'POST'] = 'GET', + mocked_request_url: str = None, + mocked_status_code: int = 200, + mocked_json_data: str = '0x', + + # required only for POST validation: + sender: str = None, + calldata: str = None, +) -> None: + class MockedResponse: + status_code = mocked_status_code + + @staticmethod + def json() -> Dict[str, str]: return {'data': mocked_json_data} # noqa: E704 + + @staticmethod + def raise_for_status() -> None: raise Exception("called raise_for_status()") # noqa: E704 + + def _mock_specific_request( + *args: Any, **kwargs: Any + ) -> Union[Response, MockedResponse]: + # mock response only to specified url while validating appropriate fields + if args[1] == mocked_request_url: + assert kwargs['timeout'] == 10 + if http_method.upper() == 'POST': + assert kwargs['data'] == {'data': calldata, 'sender': sender} + return MockedResponse() + + # else, make a normal request (no mocking) + session = requests.Session() + result = session.request( + method=http_method.upper(), + url=args[1], + **kwargs, + ) + session.close() + return result + + monkeypatch.setattr(f'requests.Session.{http_method.lower()}', _mock_specific_request) + + +# -- async -- # + +def mock_async_offchain_lookup_request_response( + monkeypatch: "MonkeyPatch", + http_method: Literal['GET', 'POST'] = 'GET', + mocked_request_url: str = None, + mocked_status_code: int = 200, + mocked_json_data: str = '0x', + + # required only for POST validation: + sender: str = None, + calldata: str = None, +) -> None: + class AsyncMockedResponse: + status = mocked_status_code + + def __await__(self) -> Generator[Any, Any, Any]: + yield + return self + + @staticmethod + async def json() -> Dict[str, str]: return {'data': mocked_json_data} # noqa: E704 + + @staticmethod + def raise_for_status() -> None: raise Exception("called raise_for_status()") # noqa: E501, E704 + + async def _mock_specific_request( + *args: Any, **kwargs: Any + ) -> Union[ClientResponse, AsyncMockedResponse]: + # mock response only to specified url while validating appropriate fields + if args[1] == mocked_request_url: + assert kwargs['timeout'] == ClientTimeout(10) + if http_method.upper() == 'post': + assert kwargs['data'] == {'data': calldata, 'sender': sender} + return AsyncMockedResponse() + + # else, make a normal request (no mocking) + async with ClientSession(raise_for_status=True) as session: + return await session.request( + method=http_method.upper(), + url=args[1], + **kwargs, + ) + + monkeypatch.setattr(f'aiohttp.ClientSession.{http_method.lower()}', _mock_specific_request) diff --git a/web3/providers/eth_tester/defaults.py b/web3/providers/eth_tester/defaults.py index 9f67523935..466e64396e 100644 --- a/web3/providers/eth_tester/defaults.py +++ b/web3/providers/eth_tester/defaults.py @@ -79,9 +79,9 @@ def call_eth_tester( return getattr(eth_tester, fn_name)(*fn_args, **fn_kwargs) except TransactionFailed as e: possible_data = e.args[0] - if isinstance(possible_data, str) and possible_data[:10] == 'b"Uo\\x180\\': + if isinstance(possible_data, str) and possible_data[2:10] == 'Uo\\x180\\': # EIP-3668 | CCIP Read - # b"Uo\\x180\\" is the function selector for: + # b"Uo\x180" is the first 4 bytes of the keccak hash for: # OffchainLookup(address,string[],bytes,bytes4,bytes) parsed_data_as_bytes = ast.literal_eval(possible_data) data_payload = parsed_data_as_bytes[4:] # everything but the function selector