canopy-network/canopy · error · ErrInvalidERC20Tx

ErrInvalidERC20Tx

ErrInvalidERC20Tx

Error message

data too short

What it means

When the RLP tx targets one of the pseudo-contract addresses, its calldata must at least contain the 4-byte ABI selector. rlpToMessage returns ErrInvalidERC20Tx('data too short') when the data is shorter than 4 bytes.

Source

Thrown at fsm/ethereum.go:178

		return tx.GasPrice(), nil
	}
}

// rlpToMessage() converts an ethereum RLP transaction to a message
func rlpToMessage(publicKey crypto.PublicKeyI, transaction *lib.Transaction, tx ethTypes.Transaction) (msg lib.MessageI, e lib.ErrorI) {
	// get the relevant tx fields
	to, from, data := tx.To(), publicKey.Address().Bytes(), tx.Data()
	// ensure non-nil to
	if to == nil {
		return nil, ErrRecipientAddressEmpty()
	}
	// switch on the 'recipient'
	switch tx.To().Hex() {
	// if the recipient is a pseudo-contract call
	case CNPYContractAddress, StakedCNPYContractAddress, SwapCNPYContractAddress:
		// ensure enough data for a selector
		if len(data) < 4 {
			return nil, ErrInvalidERC20Tx(fmt.Errorf("data too short"))
		}
		// switch on the selector
		switch selector := lib.BytesToString(data[:4]); selector {
		case SendSelector:
			msg, e = ethDataToMsgSend(from, data)
		case StakeSelector:
			m := new(MessageStake)
			msg, e = ethDataToMsg(MessageStakeName, transaction, m, data, func() {
				// allow the omission of the public key because it may be difficult to get the public key from the wallet
				if len(m.PublicKey) == 0 {
					m.PublicKey = publicKey.Bytes()
				}
			})
		case EditStakeSelector:
			m := new(MessageEditStake)
			msg, e = ethDataToMsg(MessageEditStakeName, transaction, m, data, nil)
		case UnstakeSelector:
			m := new(MessageUnstake)

View on GitHub (pinned to ee8197d91d)

Solutions

  1. ABI-encode the intended call (selector + parameters) into the tx data field before signing
  2. Do not send bare value transfers to the pseudo-contract addresses; use the proper transfer/selector call
  3. Fix the bridging tool so it forwards the full calldata

Example fix

// before
data := []byte{} // bare send to pseudo-contract
// after
selector := crypto.Keccak256("transfer(address,uint256)")[:4]
data := append(selector, abiEncodePacked(recipient, amount)...)
Defensive patterns

Strategy: validation

Validate before calling

if tx.To() != nil && isPseudoContract(tx.To()) && len(tx.Data()) < 4 {
    return fmt.Errorf("pseudo-contract calls need >= 4 byte selector; got %d", len(tx.Data()))
}

Type guard

func hasSelector(data []byte) bool { return len(data) >= 4 }

Try / catch

msg, err := fsm.RLPToCanopyTransaction(bz)
if err != nil && strings.Contains(err.Error(), "data too short") {
    return nil, fmt.Errorf("ABI-encode the pseudo-contract call before signing")
}

Prevention

When it happens

Trigger: Sending an RLP transaction to CNPYContractAddress, StakedCNPYContractAddress, or SwapCNPYContractAddress with empty or <4-byte tx.Data() — e.g. a plain value transfer addressed to the pseudo-contract.

Common situations: A wallet/user sending ETH-style value directly to the CNG contract address instead of calling transfer() with ABI-encoded calldata, or a bridge truncating the calldata.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06). Data as JSON: /api/errors/46aff50477cd0402. Report an issue: GitHub.