vyperlang / vyper

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

allow `self` as default argument #3648

Open pcaversaccio opened 1 year ago

pcaversaccio commented 1 year ago

I think it would be a nice feature to allow non-literal and built-in environment variable values as default arguments.

Example Use Case

# pragma version ^0.3.10

_COLLISION_OFFSET: constant(bytes1) = 0xFF

@external
@pure
def compute_address(salt: bytes32, bytecode_hash: bytes32, deployer: address=self) -> address:
    """
    @dev Returns the address where a contract will be stored if
         deployed via `deployer` using the `CREATE2` opcode.
         Any change in the `bytecode_hash` or `salt` values will
         result in a new destination address.
    @param salt The 32-byte random value used to create the contract
           address.
    @param bytecode_hash The 32-byte bytecode digest of the contract
           creation bytecode.
    @return address The 20-byte address where a contract will be stored.
    """
    data: bytes32 = keccak256(concat(_COLLISION_OFFSET, convert(deployer, bytes20), salt, bytecode_hash))
    return convert(convert(data, uint256) & convert(max_value(uint160), uint256), address)
charles-cooper commented 11 months ago

i think block.timestamp is allowed. i guess the only question is whether to allow self; i lean against it because it seems like an antipattern. see my notes from offline:

Charles Cooper, [11/7/23 10:18 AM]
i think the question here is if self should be an environment variable

Charles Cooper, [11/7/23 10:18 AM]
And like, it looks more like a storage variable

Charles Cooper, [11/7/23 10:18 AM]
Also its semantics could change depending on execution target

Charles Cooper, [11/7/23 10:19 AM]
Or like it could read from code or storage or something

Charles Cooper, [11/7/23 10:19 AM]
Maybe we should have context.address which compiles to ADDRESS opcode

Charles Cooper, [11/7/23 10:20 AM]
(Which happens here to be the same as self)

Charles Cooper, [11/7/23 10:20 AM]
But it's robust to changes in semantics of self
pcaversaccio commented 11 months ago

There are a couple of (library) use cases where "function overloading" using self would be helpful. What if we store the self address as an immutable at construction time as part of the code, and use this robust value for such use cases? It's not optimal for bytecode space yes, but would still enable this use case for applications that want to rely on this. This would disallow any semantic changes coming from delegatecalls.

Another approach would be having something like self.address; I once opened an issue here which is loosely connected, but elaborates the issue in the context of interfaces using .address.

charles-cooper commented 10 months ago

This would disallow any semantic changes coming from delegatecalls.

delegatecalls change code, so it could indeed have semantic changes!

pcaversaccio commented 10 months ago

Quickly linking to this VIP https://github.com/vyperlang/vyper/issues/3701 which proposes to replace self (currently has type address) with self.address or context.address. This would imply the following code pattern, which I like:

# pragma version ^0.3.10

_COLLISION_OFFSET: constant(bytes1) = 0xFF

@external
@view
def compute_address(salt: bytes32, bytecode_hash: bytes32, deployer: address=self.address) -> address:
    """
    @dev Returns the address where a contract will be stored if
         deployed via `deployer` using the `CREATE2` opcode.
         Any change in the `bytecode_hash` or `salt` values will
         result in a new destination address.
    @param salt The 32-byte random value used to create the contract
           address.
    @param bytecode_hash The 32-byte bytecode digest of the contract
           creation bytecode.
    @return address The 20-byte address where a contract will be stored.
    """
    data: bytes32 = keccak256(concat(_COLLISION_OFFSET, convert(deployer, bytes20), salt, bytecode_hash))
    return convert(convert(data, uint256) & convert(max_value(uint160), uint256), address)

If you think about it, depending on whether you use the default argument or not, the visibility modifier does implicitly change. The overloaded function without the self.address argument in the 4-byte function signature will be view (since reading from the state in the argument) and the "normal" function will be pure (in the context of the above example).

fubuloubu commented 10 months ago

Typically, instance variables in Python would not be allowed as a default argument, but I think in this case it makes sense to allow self.address, which will only rarely be used