Best way of handling `Address`es and `ContractId`s?

I am aware of the Identity type which is essentially a typed enum over Address and ContractId. For such scenarios, it provides a "neat"er abstraction over using Address and ContractId separately.

What about if you don’t care about whether an account is an Address or ContractId? I can just essentially create a wrapper type that holds the “value” of the regular EOA/contract address like so:

struct DontCareAccountType {
    value: b256
}

and everything will be hunky-dory until I attempt to transfer assets (see below).

Consider a simple Sway program as below

fn transfer_assets_to(asset_id: AssetId, recipient: Address) {
    transfer_to(asset, recipient);
}

While the Sway compiler checks the validity of the code for the most part, I can still bypass the recipient type in the frontend (via the TS SDK) and pass in a contract address for the recipient because type-wise, Address and ContractId are the same. When I do so, the function call reverts because I’m sending to a contract. I’d be forced to use the force_transfer_to_contract method for my DontCareAccountType type to accommodate both Addresses and ContractIds.

What’s the best way to handle this within my Sway code? It’s a little confusing dealing with both types (even with Identity type) until I have to deal with sending assets. You can imagine cases (like the EVM) where there’s just one address type. The obvious flaw in this thinking is that if you accidentally send assets to a contract address, there may not be anyway of retrieving those assets back. But is that it? Is there a better way?

The ContractId and Address types were introduced to improve type safety in the Sway language. As you mentioned other languages such as Solidity do not make this distinction.

There are also many cases where both entities should be handled the same way. For this, we have the Identity type.

In your example, it sounds like you would like to send funds regardless on ContractId or Address, and thus should use the Identity type as the recipient. You can use the std::asset::transfer function for this. The definition of this function is shown below:

pub fn transfer(to: Identity, asset_id: AssetId, amount: u64)

It takes an Identity as the asset recipient and will send funds regardless of Address or ContractId.

2 Likes

Understood. As mentioned, I’m already familiar with the Identity type. While I understand the type-safety the Identity type provides, is it really needed? For my requirements, we don’t really care whether our user is an EOA or another contract. Handling both via Identity is a pain both in the frontend and backend, especially since this apparent type-safety can easily be bypassed in the frontend (since, syntatically, Address and ContractId are just wrappers over a struct { value: b256 }, passing in a contract address as an Address type will cause reverts within the contract method (see example above) when sending assets (from the execution perspective, there’s an Address being passed in, and it tries to send assets to the Identity::Address type via the transfer() method, and fails to send assets.

I’m wondering if we could “eliminate” this type-safety requirement (not in the stdlib obv, but on a project-to-project scope) by creating a different type defined as:

struct Account {
    value: b256
}

and when assets are required to be sent, we use the force_send_to_contract method irrespective whether Account::value points to an EOA or contract address. Are there any other considerations/edge cases to handle besides transferring of assets where the Identity type may make sense?

1 Like

any update? @bitzoic

While I understand the type-safety the Identity type provides, is it really needed ?

Yes, in the context of Fuel and UTXOs this is required. There are two separate op codes used depending on the type; tr and tro. In the Sway language, we follow many of Rust’s principles which includes type safety.

The obvious flaw in this thinking is that if you accidentally send asset s to a contract address, there may not be anyway of retrieving those assets back.

In blockchains and dealing with value, simple mistakes such as these can have catastrophic consequences. Sway’s philosophy is to ensure these mistakes cannot happen and that the security in developing smart contracts is the highest priority.

I am unsure what you mean by difficulty sending assets in Sway when using the Identity type, as transfer() will handle both cases for you. Do you mind if you provided an example where this is a detriment to your project on the Sway and front-end side?

1 Like

yes! I completely forgot about the UTXO model for EOAs. Contracts don’t use UTXOs, so it’s a different opcode for 'em.

no difficulties in particular, but it’s easy to bypass the type-safeness by passing in an EOA hash as a ContractId on the frontend via the TS SDK. Not a biggie obviously as you said, the tx would probably just revert.

Thanks for the clarification!

1 Like

I would argue that it’s better to handle the opcode at runtime than compile time and just have one unified Identity (address in ethereum land), now developers may accidentally misuse address and lock out multisig walets (contractids)

1 Like