Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

minimal changes to allow for custom error decoding #2795

Merged
merged 5 commits into from
Apr 14, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions newsfragments/2795.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for custom revert errors
Binary file modified tests/integration/geth-1.11.5-fixture.zip
Binary file not shown.
31 changes: 21 additions & 10 deletions web3/_utils/contract_sources/RevertContract.sol
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
pragma solidity >=0.6.1;
pragma solidity ^0.8.4;

error Unauthorized();
error UnauthorizedWithMessage(string errorMessage);

contract RevertContract {
function normalFunction() public pure returns (bool) {
return true;
}
function customErrorWithoutMessage() public pure {
revert Unauthorized();
}

function customErrorWithMessage() public pure {
revert UnauthorizedWithMessage("You are not authorized");
}

function normalFunction() public pure returns (bool) {
return true;
}

function revertWithMessage() public pure {
revert('Function has been reverted.');
}
function revertWithMessage() public pure {
revert('Function has been reverted.');
}

function revertWithoutMessage() public pure {
revert();
}
function revertWithoutMessage() public pure {
revert();
}
}
28 changes: 25 additions & 3 deletions web3/_utils/contract_sources/contract_data/revert_contract.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
"""
Generated by `compile_contracts.py` script.
Compiled with Solidity v0.8.19.
Compiled with Solidity v0.8.17.
"""

# source: web3/_utils/contract_sources/RevertContract.sol:RevertContract
REVERT_CONTRACT_BYTECODE = "0x608060405234801561001057600080fd5b506101aa806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063185c38a414610046578063c06a97cb14610050578063d67e4b841461005a575b600080fd5b61004e610078565b005b6100586100b3565b005b6100626100b8565b60405161006f91906100dc565b60405180910390f35b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100aa90610154565b60405180910390fd5b600080fd5b60006001905090565b60008115159050919050565b6100d6816100c1565b82525050565b60006020820190506100f160008301846100cd565b92915050565b600082825260208201905092915050565b7f46756e6374696f6e20686173206265656e2072657665727465642e0000000000600082015250565b600061013e601b836100f7565b915061014982610108565b602082019050919050565b6000602082019050818103600083015261016d81610131565b905091905056fea26469706673582212201bcf63171d1edaa8e200af05c22b99aee2cc9f22b65c86f98f9722c252ae5ae664736f6c63430008130033" # noqa: E501
REVERT_CONTRACT_RUNTIME = "0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063185c38a414610046578063c06a97cb14610050578063d67e4b841461005a575b600080fd5b61004e610078565b005b6100586100b3565b005b6100626100b8565b60405161006f91906100dc565b60405180910390f35b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100aa90610154565b60405180910390fd5b600080fd5b60006001905090565b60008115159050919050565b6100d6816100c1565b82525050565b60006020820190506100f160008301846100cd565b92915050565b600082825260208201905092915050565b7f46756e6374696f6e20686173206265656e2072657665727465642e0000000000600082015250565b600061013e601b836100f7565b915061014982610108565b602082019050919050565b6000602082019050818103600083015261016d81610131565b905091905056fea26469706673582212201bcf63171d1edaa8e200af05c22b99aee2cc9f22b65c86f98f9722c252ae5ae664736f6c63430008130033" # noqa: E501
REVERT_CONTRACT_BYTECODE = "0x608060405234801561001057600080fd5b506102ad806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063185c38a41461005c578063bc53eca814610066578063c06a97cb14610070578063d67e4b841461007a578063e766d49814610098575b600080fd5b6100646100a2565b005b61006e6100dd565b005b610078610118565b005b61008261011d565b60405161008f9190610173565b60405180910390f35b6100a0610126565b005b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100d4906101eb565b60405180910390fd5b6040517f9553947a00000000000000000000000000000000000000000000000000000000815260040161010f90610257565b60405180910390fd5b600080fd5b60006001905090565b6040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008115159050919050565b61016d81610158565b82525050565b60006020820190506101886000830184610164565b92915050565b600082825260208201905092915050565b7f46756e6374696f6e20686173206265656e2072657665727465642e0000000000600082015250565b60006101d5601b8361018e565b91506101e08261019f565b602082019050919050565b60006020820190508181036000830152610204816101c8565b9050919050565b7f596f7520617265206e6f7420617574686f72697a656400000000000000000000600082015250565b600061024160168361018e565b915061024c8261020b565b602082019050919050565b6000602082019050818103600083015261027081610234565b905091905056fea2646970667358221220c90b36e5aeee601ff5cbcb4c09ca883f8fb491dcd68715bfe22db08d2cd76bd064736f6c63430008110033" # noqa: E501
REVERT_CONTRACT_RUNTIME = "0x608060405234801561001057600080fd5b50600436106100575760003560e01c8063185c38a41461005c578063bc53eca814610066578063c06a97cb14610070578063d67e4b841461007a578063e766d49814610098575b600080fd5b6100646100a2565b005b61006e6100dd565b005b610078610118565b005b61008261011d565b60405161008f9190610173565b60405180910390f35b6100a0610126565b005b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100d4906101eb565b60405180910390fd5b6040517f9553947a00000000000000000000000000000000000000000000000000000000815260040161010f90610257565b60405180910390fd5b600080fd5b60006001905090565b6040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008115159050919050565b61016d81610158565b82525050565b60006020820190506101886000830184610164565b92915050565b600082825260208201905092915050565b7f46756e6374696f6e20686173206265656e2072657665727465642e0000000000600082015250565b60006101d5601b8361018e565b91506101e08261019f565b602082019050919050565b60006020820190508181036000830152610204816101c8565b9050919050565b7f596f7520617265206e6f7420617574686f72697a656400000000000000000000600082015250565b600061024160168361018e565b915061024c8261020b565b602082019050919050565b6000602082019050818103600083015261027081610234565b905091905056fea2646970667358221220c90b36e5aeee601ff5cbcb4c09ca883f8fb491dcd68715bfe22db08d2cd76bd064736f6c63430008110033" # noqa: E501
REVERT_CONTRACT_ABI = [
{"inputs": [], "name": "Unauthorized", "type": "error"},
{
"inputs": [
{"internalType": "string", "name": "errorMessage", "type": "string"}
],
"name": "UnauthorizedWithMessage",
"type": "error",
},
{
"inputs": [],
"name": "customErrorWithMessage",
"outputs": [],
"stateMutability": "pure",
"type": "function",
},
{
"inputs": [],
"name": "customErrorWithoutMessage",
"outputs": [],
"stateMutability": "pure",
"type": "function",
},
{
"inputs": [],
"name": "normalFunction",
8 changes: 8 additions & 0 deletions web3/_utils/method_formatters.py
Original file line number Diff line number Diff line change
@@ -94,6 +94,7 @@
)
from web3.exceptions import (
BlockNotFound,
ContractCustomError,
ContractLogicError,
OffchainLookup,
TransactionNotFound,
@@ -757,6 +758,13 @@ def raise_contract_logic_error_on_revert(response: RPCResponse) -> RPCResponse:
)
raise OffchainLookup(offchain_lookup_payload)

# Solidity 0.8.4 introduced custom error messages that allow args to
# be passed in (or not). See:
# https://blog.soliditylang.org/2021/04/21/custom-errors/
if len(data) >= 10 and not data[:10] == "0x08c379a0":
# raising along with the data value to allow processing in user code
raise ContractCustomError(data)

# Geth case:
if "message" in response["error"] and response["error"].get("code", "") == 3:
raise ContractLogicError(response["error"]["message"])
111 changes: 111 additions & 0 deletions web3/_utils/module_testing/eth_module.py
Original file line number Diff line number Diff line change
@@ -55,6 +55,7 @@
)
from web3.exceptions import (
BlockNotFound,
ContractCustomError,
ContractLogicError,
InvalidAddress,
InvalidTransaction,
@@ -977,6 +978,44 @@ async def test_eth_call_revert_without_msg(
)
await async_w3.eth.call(txn_params)

@pytest.mark.asyncio
async def test_eth_call_revert_custom_error_with_msg(
self,
async_w3: "AsyncWeb3",
revert_contract: "Contract",
unlocked_account: ChecksumAddress,
) -> None:
data = revert_contract.encodeABI(
fn_name="UnauthorizedWithMessage", args=["You are not authorized"]
)
txn_params = revert_contract._prepare_transaction(
fn_name="customErrorWithMessage",
transaction={
"from": unlocked_account,
"to": revert_contract.address,
},
)
with pytest.raises(ContractCustomError, match=data):
await async_w3.eth.call(txn_params)

@pytest.mark.asyncio
async def test_eth_call_revert_custom_error_without_msg(
self,
async_w3: "AsyncWeb3",
revert_contract: "Contract",
unlocked_account: ChecksumAddress,
) -> None:
data = revert_contract.encodeABI(fn_name="Unauthorized")
txn_params = revert_contract._prepare_transaction(
fn_name="customErrorWithoutMessage",
transaction={
"from": unlocked_account,
"to": revert_contract.address,
},
)
with pytest.raises(ContractCustomError, match=data):
await async_w3.eth.call(txn_params)

@pytest.mark.asyncio
async def test_eth_call_offchain_lookup(
self,
@@ -3225,6 +3264,42 @@ def test_eth_call_revert_without_msg(
)
w3.eth.call(txn_params)

def test_eth_call_custom_error_revert_with_msg(
self,
w3: "Web3",
revert_contract: "Contract",
unlocked_account: ChecksumAddress,
) -> None:
data = revert_contract.encodeABI(
fn_name="UnauthorizedWithMessage", args=["You are not authorized"]
)
txn_params = revert_contract._prepare_transaction(
fn_name="customErrorWithMessage",
transaction={
"from": unlocked_account,
"to": revert_contract.address,
},
)
with pytest.raises(ContractCustomError, match=data):
w3.eth.call(txn_params)

def test_eth_call_custom_error_revert_without_msg(
self,
w3: "Web3",
revert_contract: "Contract",
unlocked_account: ChecksumAddress,
) -> None:
data = revert_contract.encodeABI(fn_name="Unauthorized")
txn_params = revert_contract._prepare_transaction(
fn_name="customErrorWithoutMessage",
transaction={
"from": unlocked_account,
"to": revert_contract.address,
},
)
with pytest.raises(ContractCustomError, match=data):
w3.eth.call(txn_params)

def test_eth_call_offchain_lookup(
self,
w3: "Web3",
@@ -3458,6 +3533,42 @@ def test_eth_estimate_gas_revert_without_msg(
)
w3.eth.estimate_gas(txn_params)

def test_eth_estimate_gas_custom_error_revert_with_msg(
self,
w3: "Web3",
revert_contract: "Contract",
unlocked_account: ChecksumAddress,
) -> None:
data = revert_contract.encodeABI(
fn_name="UnauthorizedWithMessage", args=["You are not authorized"]
)
txn_params = revert_contract._prepare_transaction(
fn_name="customErrorWithMessage",
transaction={
"from": unlocked_account,
"to": revert_contract.address,
},
)
with pytest.raises(ContractCustomError, match=data):
w3.eth.estimate_gas(txn_params)

def test_eth_estimate_gas_custom_error_revert_without_msg(
self,
w3: "Web3",
revert_contract: "Contract",
unlocked_account: ChecksumAddress,
) -> None:
data = revert_contract.encodeABI(fn_name="Unauthorized")
txn_params = revert_contract._prepare_transaction(
fn_name="customErrorWithoutMessage",
transaction={
"from": unlocked_account,
"to": revert_contract.address,
},
)
with pytest.raises(ContractCustomError, match=data):
w3.eth.estimate_gas(txn_params)

def test_eth_estimate_gas(
self, w3: "Web3", unlocked_account_dual_type: ChecksumAddress
) -> None:
6 changes: 6 additions & 0 deletions web3/exceptions.py
Original file line number Diff line number Diff line change
@@ -251,6 +251,12 @@ class ContractLogicError(Web3Exception):
"""


class ContractCustomError(ContractLogicError):
"""
Raised on a contract revert custom error
"""


class OffchainLookup(ContractLogicError):
"""
Raised when a contract reverts with OffchainLookup as described in EIP-3668