@ -1,15 +1,18 @@
from typing import TYPE_CHECKING
from trezor . crypto import rlp
from trezor . messages import Ethereum TxRequest
from trezor . messages import Ethereum NetworkInfo, Ethereum TxRequest
from trezor . wire import DataError
from . helpers import bytes_from_address
from . helpers import address_from_bytes, bytes_from_address
from . keychain import with_keychain_from_chain_id
if TYPE_CHECKING :
from typing import Any
from apps . common . keychain import Keychain
from trezor . messages import EthereumSignTx , EthereumTxAck , EthereumTokenInfo
from trezor . ui . layouts . common import PropertyType
from . definitions import Definitions
from . keychain import MsgInSignTx
@ -93,50 +96,81 @@ async def sign_tx_inner(
) - > tuple [ EthereumTokenInfo | None , bytes , bytes , int | None ] :
from . import tokens
from . layout import (
require_confirm_approve_tx ,
require_confirm_data ,
require_confirm_tx ,
require_confirm_unknown_token ,
)
from trezor . ui . layouts import confirm_properties
from trezor . enums import ButtonRequestType
from apps . common import paths
from ubinascii import hexlify
check_common_fields ( msg )
await paths . validate_path ( keychain , msg . address_n )
data_initial_chunk = msg . data_initial_chunk # local_cache_attribute
data_initial_chunk = memoryview ( msg . data_initial_chunk ) # local_cache_attribute
token = None
address_bytes = recipient = bytes_from_address ( msg . to )
value = int . from_bytes ( msg . value , " big " )
FUNCTION_HASH_LENGTH = (
4 # ETH function hashes are truncated to 4 bytes in calldata for some reason.
)
if (
len ( msg . to ) in ( 40 , 42 )
and len ( msg . value ) == 0
and msg . data_length == 68
and len ( data_initial_chunk ) == 68
and msg . data_length >= FUNCTION_HASH_LENGTH
and len ( data_initial_chunk ) >= FUNCTION_HASH_LENGTH
) :
initial_prefix = data_initial_chunk [ : 16 ]
# See https://github.com/ethereum-lists/4bytes/blob/master/signatures/a9059cbb etc
ETH_ERC20_TRANSFER = (
b " \xa9 \x05 \x9c \xbb \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 "
)
ETH_ERC20_APPROVE = (
b " \x09 \x5e \xa7 \xb3 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 "
)
if initial_prefix == ETH_ERC20_TRANSFER :
token = defs . get_token ( address_bytes )
recipient = data_initial_chunk [ 16 : 36 ]
value = int . from_bytes ( data_initial_chunk [ 36 : 68 ] , " big " )
if token is tokens . UNKNOWN_TOKEN :
await require_confirm_unknown_token ( address_bytes )
elif initial_prefix == ETH_ERC20_APPROVE :
token = defs . get_token ( address_bytes )
recipient = data_initial_chunk [ 16 : 36 ]
# We are not transferring any value, just setting an allowance.
value = None
allowance = int . from_bytes ( data_initial_chunk [ 36 : 68 ] , " big " )
await require_confirm_approve_tx ( recipient , allowance , defs . network , token )
function_hash = int . from_bytes ( data_initial_chunk [ : FUNCTION_HASH_LENGTH ] , " big " )
if value :
raise DataError (
" Expecting zero or None as native transaction value when calling smart contract functions "
)
# Force 0 value to None to signify that we are not spending regular ethereum in later dialogs
value = None
confirm_props : list [ PropertyType ] = [ ]
# Bother the user with confirming unknown tokens if we are indeed calling a function and not just transferring value.
token = defs . get_token ( address_bytes )
if token is tokens . UNKNOWN_TOKEN :
await require_confirm_unknown_token ( address_bytes , defs . network )
else :
confirm_props . append ( ( " Token: " , token . name ) )
if defs . network :
confirm_props . append ( ( " Network: " , defs . network . name ) )
if abi := _fetch_function_abi ( function_hash ) :
confirm_props . extend (
_fetch_abi_input_properties (
abi [ " inputs " ] ,
data_initial_chunk [ FUNCTION_HASH_LENGTH : ] ,
defs . network ,
)
)
await confirm_properties (
" confirm_call " ,
abi [ " description " ] ,
confirm_props ,
hold = True ,
br_code = ButtonRequestType . SignTx ,
)
elif token :
confirm_props . append (
( " Raw calldata: " , f " 0x { hexlify ( data_initial_chunk ) . decode ( ) } " )
)
await confirm_properties (
" confirm_unknown_abi " ,
" Confirm calling function " ,
confirm_props ,
hold = True ,
br_code = ButtonRequestType . SignTx ,
)
if value is not None :
await require_confirm_tx ( recipient , value , defs . network , token )
@ -147,6 +181,108 @@ async def sign_tx_inner(
return token , address_bytes , recipient , value
def _fetch_function_abi ( function_hash : int ) - > dict [ str , Any ] | None :
# Current limitations:
# * Only contains ERC-20 functions
# * Does not include outputs, not shown in UI
# In the future this lookup could be provided by the host, signed with SL keys.
ABI_DB : dict [ int , dict [ str , Any ] ] = {
0xDD62ED3E : {
" name " : " allowance " ,
" description " : " ERC 20 Fetch allowance amount " ,
" inputs " : [
{ " name " : " owner " , " type " : " address " } ,
{ " name " : " spender " , " type " : " address " } ,
] ,
} ,
0x095EA7B3 : {
" name " : " approve " ,
" description " : " ERC 20 Approve allowance " ,
" inputs " : [
{
" name " : " spender " ,
" type " : " address " ,
} ,
{
" name " : " value " ,
" type " : " uint256 " ,
} ,
] ,
} ,
0xA9059CBB : {
" name " : " transfer " ,
" description " : " ERC 20 Transfer " ,
" inputs " : [
{
" name " : " to " ,
" type " : " address " ,
} ,
{
" name " : " value " ,
" type " : " uint256 " ,
} ,
] ,
} ,
0x23B872DD : {
" name " : " transferFrom " ,
" description " : " ERC 20 Transfer from " ,
" inputs " : [
{
" name " : " from " ,
" type " : " address " ,
} ,
{
" name " : " to " ,
" type " : " address " ,
} ,
{
" name " : " value " ,
" type " : " uint256 " ,
} ,
] ,
} ,
}
return ABI_DB . get ( function_hash )
def _fetch_abi_input_properties (
input_defs : list [ dict [ str , str ] ] , calldata : memoryview , network : EthereumNetworkInfo
) - > list [ PropertyType ] :
expected_data_length = len ( input_defs ) * 32
if len ( calldata ) != expected_data_length :
raise DataError (
f " Function definition requires more parameter data than provided in calldata ( { len ( calldata ) } vs { expected_data_length } ). "
)
input_props : list [ PropertyType ] = [ ]
for index , input_def in enumerate ( input_defs ) :
input_type = input_def [ " type " ]
input_name = input_def [ " name " ]
if input_type == " address " :
# We skip over the first 12 bytes of zero padding in the address
input_address = calldata [ 12 + ( index * 32 ) : 32 + ( index * 32 ) ]
input_props . append (
(
f " { input_name } ( { input_type } ): " ,
address_from_bytes ( input_address , network ) ,
)
)
elif input_type == " uint256 " :
input_uint = int . from_bytes (
calldata [ ( index * 32 ) : 32 + ( index * 32 ) ] , " big "
)
input_props . append ( ( f " { input_name } ( { input_type } ): " , str ( input_uint ) ) )
else :
raise DataError (
" Currently unsupported type returned by _fetch_function_abi: "
+ input_type
)
return input_props
def _get_total_length ( msg : EthereumSignTx , data_total : int ) - > int :
length = 0
if msg . tx_type is not None :