vyperlang / vyper

Pythonic Smart Contract Language for the EVM
https://vyperlang.org
Other
4.84k stars 789 forks source link

Vyper bool doesn't match the SolidityProxy bool variable #2270

Closed lebed2045 closed 3 years ago

lebed2045 commented 3 years ago

I'm not sure whether it's a bug or my lack of experience with Vyper

What's your issue about?

I have very simple Vyper contract

a: public(address)
b: public(bool)
c: public(uint256)

@external
def __init__(_a: address):
    self.a = _a
    self.c = block.timestamp + 2

@external
def set_b_true():
    self.b = True

and Solidity delegator with OZ proxy full code

contract SolidityProxy {
    address public a;
    bool public b;
    uint256 public c;

    constructor(address _a) public {
        a = _a;
        c = block.timestamp + 2;
    }
 // The proxy code is from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol
...

now, if you call set_b_true via Proxy - your c variable would 1, but b won't be affected. You might think the the proxy code is wrong, but I tried two different impl and both worked with everything except bool variable, including arrays (but array with constant size in Vyper somehow only work with array with dynamic size in Solidity proxy so I assume it's my lack of understanding of laguages?)

const VyperContract = artifacts.require('VyperContract');
const SolidityProxy = artifacts.require('SolidityProxy');

contract('test', (accounts) => {

    beforeEach(async () => {
        const [a] =  accounts;
        this.vyperContract = await VyperContract.new(a);
        this.solidityProxy = await SolidityProxy.new(a, this.vyperContract.address);
        this.solidityProxy = await VyperContract.at(this.solidityProxy.address);
    });

    const getState = async (myContract) =>  {
        const a = ((await myContract.a()).toString());
        const b = ((await myContract.b()).toString());
        const c = ((await myContract.c()).toString());
        return {a, b, c};
    }

    it("compareStates", async() => {
        // since it's the contracts the same - you would expect the variables are the same as well
        console.log(await getState(this.vyperContract));
        // {
        //     a: '0xA51043a9afD5e8Fd2869b2cb6d83b568650CB4A4',
        //     b: 'false',
        //     c: '1609608804'
        // }
        console.log(await getState(this.solidityProxy));
        // {
        //     a: '0xA51043a9afD5e8Fd2869b2cb6d83b568650CB4A4',
        //     b: 'false',
        //     c: '1609608805'
        // }

        await this.solidityProxy.set_b_true();
        console.log(await getState(this.solidityProxy));
        // { a: '0xA51043a9afD5e8Fd2869b2cb6d83b568650CB4A4', b: 'false', c: '1' }
        // different result is expected!!!
    });
});

Version Information

How can it be fixed?

I think the example of working proxy would be great? I don't know

fubuloubu commented 3 years ago

The fundamental problem here is that Vyper's and Solidity's storage allocation algorithms are deeply incompatible, and there exists no standard for how storage allocation should work or be cross-language compatible.

We have some ideas for a robust standardization (see VIP #1733), but none of them are practical yet, and even if they were, Solidity would have to adopt the same standard for them to be interoperable.

Here is a great blog post to understand the dangers of upgradeability patterns, which is some of the reasoning behind why Vyper does not support what you are doing, and probably will never support, as it is a brittle pattern. https://blog.trailofbits.com/2018/09/05/contract-upgrade-anti-patterns/

Closing this for now, I'm sorry we could not resolve your issue in the way you expected, but that is by design