OffchainLabs / stylus-sdk-rs

Rust Smart Contracts on Arbitrum
249 stars 82 forks source link

Storage access issues with multi-level inheritance #106

Open gwalen opened 9 months ago

gwalen commented 9 months ago

Stylus version: 0.4.2

Contract structure

I have 3 contracts:

Erc20 is standard erc20 impl, I will omit the methods implementation, just mention the ones used test cases:

sol_storage! {
  pub struct Erc20<T> {
      mapping(address => uint256) balances;
      mapping(address => mapping(address => uint256)) allowances;
      uint256 total_supply;
      PhantomData<T> phantom;
  }
}

impl<T: Erc20Params> Erc20<T> {

    pub fn mint(&mut self, account: Address, value: U256) -> Result<(), Erc20Error> { .... }

    pub fn burn(&mut self, account: Address, value: U256) -> Result<(), Erc20Error> { ....}

    pub fn update(&mut self, from: Address, to: Address, value: U256) -> Result<(), Erc20Error> { .... }
}

#[external]
impl<T: Erc20Params> Erc20<T> {
  ...
    pub fn balance_of(&self, address: Address) -> Result<U256, Erc20Error> {
        Ok(self.balances.get(address))
    }
  ... (other methods are also implemented)
}

Erc20Burnable is standard Erc20 extension link to solidity impl here

sol_storage! {
    pub struct Erc20Burnable  {
        #[borrow]
        Erc20<MyTokenParams> erc20;
    }
}

#[external]
#[inherit(Erc20<MyTokenParams>)]
impl Erc20Burnable  {

    pub fn burn(&mut self, account: Address, amount: U256) -> Result<(), Erc20Error> {
        self.erc20.burn(account, amount)
    }

    pub fn balance_of_burn(&self, address: Address) -> Result<U256, Erc20Error> {
        Ok(self.erc20.balances.get(address))
    }
}

MyToken is the concreate contract impl that extends Erc20 and Erc20Burnable

sol_storage! {
    #[entrypoint]
    pub struct MyToken {
        #[borrow]
        Erc20<MyTokenParams> erc20;
        #[borrow]
        Erc20Burnable erc20_burnable;
    }
}

#[external]
#[inherit(Erc20<MyTokenParams>, Erc20Burnable)]
impl MyToken {

    pub fn mint(&mut self, account: Address, amount: U256) -> Result<(), Erc20Error> {
        self.erc20.mint(account, amount)
    }

    pub fn balance_of_burn_erc(&self, address: Address) -> Result<U256, Erc20Error> {
        self.erc20_burnable.balance_of_burn(address)
    }

    pub fn balance_of_direct(&self, address: Address) -> Result<U256, Erc20Error> {
        Ok(self.erc20.balances.get(address))
    }
}

Issue

After minting tokens with MyToken::mint(..) when trying to access balances from Erc20Burnable methods instead of actual value the call returns 0. It looks like it can not access right storage. Contract code is called correctly (I checked with throwing errors inside the Erc20 methods) but storage is not accessed in proper way.

simplified code (I'm using ether-rs but full test will take a lot of lines)

// balance taken with Erc20 balance_of method
let alice_balance = token.balance_of_burn(alice_address).call().await.unwrap();

// balance taken with call to Erc20Burnable method 
let alice_balance_burn = token.balance_of_burn(alice_address).call().await.unwrap();

// balance taken from MyToken method calling Erc20Burnable method
let alice_balance_of_burn_erc = token.balance_of_burn_erc(alice_address).call().await.unwrap();

// balance taken from MyToken method directly reading Erc20 blances mapping
let alice_balance_of_direct = token.balance_of_direct(alice_address).call().await.unwrap();

All methods being called on Erc20Burnable (value of alice_balance_burn, alice_balance_of_burn_erc) are returning 0