Open MujkicA opened 1 year ago
TLDR: (Highly recommend reading the thread)
Fundamentally, ANY action done by a wallet should be able to be done by a DAO/multisig (full stop, no exceptions). The current scheme means that developers need to have the foresight to allow contractIds (multisgs) to be composable. You can recommend Identity everywhere sure, but this still introduces the potential for composability bugs + makes testing contracts very hard. If this distinction is not a value ADD then please remove it.
This is a critical mistake, let's ensure that DAOs / Multisigs are not second class citizens in Fuel by removing this distinction. This would actually be a step in the opposite direction with respect to account abstraction, and smart contract wallets.
Having read the thread I definitely understand your concerns @diyahir, however I initially find myself agreeing with @MujkicA 's comment:
However, my opinion remains that for a smart contract, any choice of permitting additional functionality/use-cases should always be deliberate instead of implied. I think this is better addressed trough proper documentation.
It seems as though this is a case of understandable user-error.
Having said that, both Address
and ContractId
are just wrappers around a b256
:
/// The `ContractId` type, a struct wrapper around the inner `b256` value.
pub struct ContractId {
value: b256,
}
There's nothing to enforce that the wrapped b256
corresponds to the correct type; you can have the b256
of a multisig wrapped within an Address
.
Seems like we want three things here:
I would say we should at least:
Address
and ContractId
are less permissive than one would expectFrom
implementations to make the proper conversions easyAddress
to something that is more explicit about the restrictionThese are all a step in the right direction! But this whole distinction is still in my opinion flawed.
Can you name me one concrete example where you do not want a Multi-sig / DAO contract to interface with your protocol? If the answer is no, then just remove this. (I cannot come up with one). This feels a bit over-engineered, no one was asking for this 'feature'.
Secondly, the industry is moving towards account abstraction with smart contract wallets, this decision will mean that there is a chance that my smart contract wallet cannot do all of the things that my normal wallet can? This was never the intention.
Documentation is good, but we're embedding a 'gotcha' into the system, which I have to really push back on. (Oh well you didn't read the one paragraph in the docs, that's a 'you' problem). This is a fundamental error, which is better to find in beta than in prod.
There's nothing to enforce that the wrapped
b256
corresponds to the correct type; you can have theb256
of a multisig wrapped within anAddress
.
This comment confuses me, I now really don't understand what the whole point of this identity, contracted, address song, and dance was for?
This comment suggests, it's not enforced, it's superficial, and is annoying to deal with, so what exactly are we doing here?..
You seem to be of the opinion that there is no circumstance in which distinguishing wallet and contract addresses is useful and therefore that expressing that is pure overhead.
I don't have strong feelings about this but it seems strange to me given the VM treats outputs and contracts differently.
Consider the following code from std
:
pub fn transfer(amount: u64, asset_id: AssetId, to: Identity) {
match to {
Identity::Address(addr) => transfer_to_address(amount, asset_id, addr),
Identity::ContractId(id) => force_transfer_to_contract(amount, asset_id, id),
};
}
// ...
pub fn force_transfer_to_contract(amount: u64, asset_id: AssetId, to: ContractId) {
asm(r1: amount, r2: asset_id.value, r3: to.value) {
tr r3 r1 r2;
}
}
// ...
pub fn transfer_to_address(amount: u64, asset_id: AssetId, to: Address) {
// maintain a manual index as we only have `while` loops in sway atm:
let mut index = 0;
// If an output of type `OutputVariable` is found, check if its `amount` is
// zero. As one cannot transfer zero coins to an output without a panic, a
// variable output with a value of zero is by definition unused.
let number_of_outputs = output_count();
while index < number_of_outputs {
if let Output::Variable = output_type(index) {
if output_amount(index) == 0 {
asm(r1: to.value, r2: index, r3: amount, r4: asset_id.value) {
tro r1 r2 r3 r4;
};
return;
}
}
index += 1;
}
revert(FAILED_TRANSFER_TO_ADDRESS_SIGNAL);
}
I'm open to the idea, but no one has provided a compelling example or justification as of yet. The only examples I can come up with are refuted by multisig smart contracts, smart contract wallets, and DAOs should have the same functionality full stop as a wallet, and we don't want to introduce weird casting just to introduce weird casting.
If the VM must know the type, then maybe it should be a hidden fact like in EVM: the fact that it's an EOA is effectively completely abstracted away
The code you're showing me just tells me that places in the stack, have followed the convention introduced, but not that it was a meaningful distinction. This is by definition what tech debt looks like, 'we cannot remove X, because look we´ve used X here and there´.
wallet.force_transfer_to_contract(to, balance, asset_id, tx_parameters).await
wallet.transfer(to, amount, asset_id, tx_parameters).await
With this distinction just transferring tokens is more overhead...this is not useful at all
The EVM doesn't use UTXOs though and that's a fundamental design choice. I can't speak to the necessity of having two different transfer instructions, but I doubt we have two for the sake of having two.
So long as transferring funds is a different process for either type we have to be told which type it is. Otherwise it's impossible to deduce what to do, and the safe way to carry this information is through the type system.
I hear that the conversions are annoying and the meaning of the types unclear, hence my previous recommendations to make that less painful. But I can't agree that carrying the information is useless unless it can be demonstrated that the VM has the ability to hide this distinction and retain it's performance characteristics.
In any case, that part of it is not a language design issue.
Can you tag a few others for more input? @IGI-111
I'm trying to understand the reasoning a bit deeper. To me with freshish eyes, it looks like 'it was just built this way' rather than an intentional value-add distinction.
A compelling example would go a long way to add more flavor to the discussion. Key stakeholders here are normal users, daos, multisigs, smart contract wallets (social recovery etc), and other protocols (like structured product vaults that hook into existing products)
In solidity you can do this: https://ethereum.stackexchange.com/questions/15641/how-does-a-contract-find-out-if-another-address-is-a-contract
To check if contract during run-time, which seems like it would translate to FVM as well
From this discussion https://github.com/FuelLabs/fuels-rs/issues/952 it was suggested that the distinction between Identity/Contract/Address represents a risk factor for people building with sway.
Unless the developers have the foresight to use Identity as the type for their admin address, they will not be composable with other contracts.
There seems to be a considerable risk associated with as developers coming from other ecosystems are likely to assume that there is no such limitations when just using
Address
.