github.com/lmittmann/w3@v0.20.0/func.go (about)

     1  package w3
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/ethereum/go-ethereum/accounts/abi"
     9  	"github.com/ethereum/go-ethereum/crypto"
    10  	_abi "github.com/lmittmann/w3/internal/abi"
    11  )
    12  
    13  var (
    14  	ErrInvalidABI       = errors.New("w3: invalid ABI")
    15  	ErrArgumentMismatch = errors.New("w3: argument mismatch")
    16  	ErrReturnsMismatch  = errors.New("w3: returns mismatch")
    17  	ErrInvalidType      = errors.New("w3: invalid type")
    18  	ErrEvmRevert        = errors.New("w3: evm reverted")
    19  
    20  	revertSelector       = selector("Error(string)")
    21  	outputSuccess        = B("0x0000000000000000000000000000000000000000000000000000000000000001")
    22  	approveSelector      = selector("approve(address,uint256)")
    23  	transferSelector     = selector("transfer(address,uint256)")
    24  	transferFromSelector = selector("transferFrom(address,address,uint256)")
    25  )
    26  
    27  // Func represents a Smart Contract function ABI binding.
    28  //
    29  // Func implements the [w3types.Func] interface.
    30  type Func struct {
    31  	Signature string        // Function signature
    32  	Selector  [4]byte       // 4-byte selector
    33  	Args      abi.Arguments // Arguments (input)
    34  	Returns   abi.Arguments // Returns (output)
    35  
    36  	name string // Function name
    37  }
    38  
    39  // NewFunc returns a new Smart Contract function ABI binding from the given
    40  // Solidity function signature and its returns.
    41  //
    42  // The optional tuples parameter accepts struct definitions that can be
    43  // referenced by name in the signature instead of using inline tuple
    44  // definitions. This enables cleaner, more readable function signatures when
    45  // working with complex tuple types.
    46  //
    47  // Examples:
    48  //
    49  //	// Without named tuples (inline)
    50  //	NewFunc("transfer((address,uint256))", "bool")
    51  //
    52  //	// With named tuple
    53  //	type Token struct { To common.Address; Amount *big.Int }
    54  //	NewFunc("transfer(Token)", "bool", Token{})
    55  //
    56  // An error is returned if the signature or returns parsing fails.
    57  func NewFunc(signature, returns string, tuples ...any) (*Func, error) {
    58  	name, args, err := _abi.ParseWithName(signature, tuples...)
    59  	if err != nil {
    60  		return nil, fmt.Errorf("%w: %v", ErrInvalidABI, err)
    61  	}
    62  	if name == "" {
    63  		return nil, fmt.Errorf("%w: missing function name", ErrInvalidABI)
    64  	}
    65  
    66  	returnArgs, err := _abi.Parse(returns, tuples...)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("%w: %v", ErrInvalidABI, err)
    69  	}
    70  
    71  	sig := args.SignatureWithName(name)
    72  	return &Func{
    73  		Signature: sig,
    74  		Selector:  selector(sig),
    75  		Args:      abi.Arguments(args),
    76  		Returns:   abi.Arguments(returnArgs),
    77  		name:      name,
    78  	}, nil
    79  }
    80  
    81  // MustNewFunc is like [NewFunc] but panics if the signature or returns parsing
    82  // fails.
    83  func MustNewFunc(signature, returns string, tuples ...any) *Func {
    84  	fn, err := NewFunc(signature, returns, tuples...)
    85  	if err != nil {
    86  		panic(err)
    87  	}
    88  	return fn
    89  }
    90  
    91  // EncodeArgs ABI-encodes the given args and prepends the Func's 4-byte
    92  // selector.
    93  func (f *Func) EncodeArgs(args ...any) ([]byte, error) {
    94  	return _abi.Arguments(f.Args).EncodeWithSelector(f.Selector, args...)
    95  }
    96  
    97  // DecodeArgs ABI-decodes the given input to the given args.
    98  func (f *Func) DecodeArgs(input []byte, args ...any) error {
    99  	if len(input) < 4 {
   100  		return errors.New("w3: insufficient input length")
   101  	}
   102  	if !bytes.Equal(input[:4], f.Selector[:]) {
   103  		return errors.New("w3: input does not match selector")
   104  	}
   105  	return _abi.Arguments(f.Args).Decode(input[4:], args...)
   106  }
   107  
   108  // DecodeReturns ABI-decodes the given output to the given returns.
   109  func (f *Func) DecodeReturns(output []byte, returns ...any) error {
   110  	// check the output for a revert reason
   111  	if bytes.HasPrefix(output, revertSelector[:]) {
   112  		if reason, err := abi.UnpackRevert(output); err != nil {
   113  			return err
   114  		} else {
   115  			return fmt.Errorf("%w: %s", ErrEvmRevert, reason)
   116  		}
   117  	}
   118  
   119  	// Gracefully handle uncompliant ERC20 returns
   120  	if len(returns) == 1 && len(output) == 0 &&
   121  		(f.Selector == approveSelector ||
   122  			f.Selector == transferSelector ||
   123  			f.Selector == transferFromSelector) {
   124  		output = outputSuccess
   125  	}
   126  
   127  	return _abi.Arguments(f.Returns).Decode(output, returns...)
   128  }
   129  
   130  // selector returns the 4-byte selector of the given signature.
   131  func selector(signature string) (selector [4]byte) {
   132  	copy(selector[:], crypto.Keccak256([]byte(signature)))
   133  	return
   134  }