-
Notifications
You must be signed in to change notification settings - Fork 109
Description
Supertraits and associated types are useful when defining inheritance relationships and overriding "parent" functions. Unfortunately, neither are fully supported by the SDK yet.
The below implementation does not work:
trait IErc20 {
type Error;
fn transfer(&mut self, to: Address, value: U256) -> Result<bool, Self::Error>;
// other funcs...
}
trait IErc20Burnable: IErc20 {
fn burn(&mut self, value: U256) -> Result<(), Self::Error>;
}
// ...
sol! {
#[derive(Debug)]
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
// other errors...
}
#[derive(SolidityError, Debug)]
pub enum Error {
InsufficientBalance(ERC20InsufficientBalance),
// other errors...
}
#[storage]
#[entrypoint]
struct Erc20BurnableExample {
balances: StorageMap<Address, StorageU256>,
allowances: StorageMap<Address, StorageMap<Address, StorageU256>>,
total_supply: StorageU256,
}
#[public]
#[implements(IErc20<Error = Error>, IErc20Burnable<Error = Error>)] // setting assoc. type for `IErc20Burnable` is necessary
impl Erc20BurnableExample {}
#[public]
impl IErc20 for Erc20BurnableExample {
type Error = Error;
fn transfer(&mut self, to: Address, value: U256) -> Result<bool, Self::Error> {
// implementation
Ok(true)
}
// other func implementations...
}
#[public]
impl IErc20Burnable for Erc20BurnableExample {
// ^^^^^^^^^^^^^^^
// value of the associated type `Error` in `IErc20` must be specified
fn burn(&mut self, value: U256) -> Result<(), Self::Error> {
// implementation
Ok(())
}
}
The issue seems to be that public
at some point generates a dyn IErc20Burnable
that needs to actually be dyn IErc20Burnable<Error = Error>
.
But if we set that in any way, we get a compiler error:
#[public]
impl IErc20Burnable<Error = Error> for Erc20BurnableExample {
// ^^^^^^^^^^^^^^
// associated item constraints are not allowed here
or:
#[public]
impl IErc20Burnable for Erc20BurnableExample {
type Error = Error;
// ^^^^^^^^^^^^^^^^^
// `type Error` is not a member of trait `IErc20Burnable`
Consider that the #[public]
macro should also work with traits having multiple supertraits with associated types. Currently this errors out:
trait IErc20 {
type Error;
fn transfer(
&mut self,
to: Address,
value: U256,
) -> Result<bool, Self::Error>;
// other funcs...
}
trait IPausable {
type Error;
fn pause(&mut self) -> Result<(), Self::Error>;
}
trait IErc20Burnable: IErc20 + IPausable {
type Error: From<<Self as IErc20>::Error> + From<<Self as IPausable>::Error>;
fn burn(
&mut self,
value: U256,
) -> Result<(), <Self as IErc20Burnable>::Error>;
}
// other defs and impls...
#[public]
// ^^^^^^
// the value of the associated types `Error` in `IErc20`, `Error` in `IPausable` must be specified
// consider introducing a new type parameter, adding `where` constraints using the fully-qualified path to the associated types
impl IErc20Burnable for Erc20BurnableExample {
type Error = Error;
fn burn(
&mut self,
value: U256,
) -> Result<(), <Self as IErc20Burnable>::Error> {
// implementation
Ok(())
}
}
A dev-friendly solution would be to have an additional attribute to set under #[public]
macro that sets the appropriate associated types for supertraits:
#[public]
#[associated_types(IErc20: Error = Error)]
impl IErc20Burnable for Erc20BurnableExample {
fn burn(&mut self, value: U256) -> Result<(), Self::Error> {
Ok(())
}
}
If there are multiple supertraits, with potentially multiple associated types, they could all be comma-separated:
#[public]
#[associated_types(
ISuperTrait: Key = String, Value = Vec<u8>,
AnotherSupertrait: ExpiryTime = u64
)]
impl ChildTrait for Contract {
// ...
}
This is just a suggestion, maybe there are more dev-friendly ways to implement this. I'm afraid verbosity is the price of Rust's type-safety.