There are several reasons why a syntax for defining multiple contracts in the same file, like the one below, would be very useful:
contract Wallet:
def __init__(owner):
self.owner = owner
....
It will encourage decomposition once Viper supports some way to compose contract functionality together (does not have to be inheritance). Instead of having to put all the code in the same namespace, one can define multiple composable components and wire them together, referring to them with their names.
If we have to rely on the filename to refer to a contract, it will make it harder for some platforms (like Etherscan) to properly display code involving multiple components (and thus multiple files).

I agree with the premise of this issue, that in order to design larger applications the ability segment out and link different functionalities implemented in separate contracts becomes very necessary. Implementation of this specific feature requires a lot of discussion to ensure a secure and correct design on our part (most hacks relate to external code linking).
I'm not sure if your specific proposal of allowing encapsulation of contracts via a simple contract MyContract: ... would be the way to go to this end. In fact, we have external calling capabilities that, while a bit crude, enforce more strict guarantees than what is possible in Solidity, and I think that is a good thing. I've imagined how we might open up that capability in a more clear way in order to discourage too much abstraction (thereby remaining audit-friendly, a major design goal) but allow more powerful sets of Viper contracts.
As of right now, it is possible to create larger multi-file programs in Viper just from the external calling capabilities and a sophisticated deployment strategy, so let's make this issue a starting place for how we can make that process as clear, concise, secure and auditable as possible.
Hi @fubuloubu, I think I found some examples of the current external calling implementation inside the tests:
class Foo():
def array() -> bytes <= 3: pass
def get_array(arg1: address) -> bytes <= 3:
return Foo(arg1).array()
You mention that this style provides more strict guarantees than what is possible in Solidity. Could you elaborate a bit on that or point me to a place where I can learn more about it? Thanks in advance.
We disallow inheritance, and enforce type alignment a little better outside of runtime. @DavidKnott is still working on some things relating to it, I believe.
Personally, I prefer the mantra of "do one thing and do it well" which is basically enforced by only allowing one contract per file.
Currently we only support one contract effectively per file (first level indent is basically a class), at some point the discussion of defining multiple contracts per file came up, I envisioned something like this - (reads a lot more like python) :
class Foo(ExternalContract):
def __init__():
pass
def foo(arg1: num) -> num:
pass
class BooBoo(Contract):
baby_contracts: address[num]
contract_count: num
def make_foo(self, arg1: address, arg2: num) -> num:
new_foo = Foo(arg2)
self.baby_contracts[self.contract_count] = new_foo
self.contract_countt += 1
return new_foo
class SomeOtherContract(Contract):
...
I am also a big proponent of having self passed into the function, because it means self isn't a mystery variable (and it follows the python convention).
Also +1 for not allowing inheritance, above syntax would not allow one contract to inherit from another.
+1 on being explicit about self. You can also use the existence of the self parameter to distinguish calls that modify state from those that are pure.
Do you envision some way to reuse code that is not inheritance? I mentioned to @DavidKnott during the DEVCON3 that the canonical answer to this is some mechanism for composition (see Go for an example of a language that explicitly favors composition as opposed to inheritance) but there might be some other interesting models out there.
I'm of the opposite opinion here haha. I think the classic python syntax MyContract(OtherContract) is asking for thinking about it as inheritance (I know we can disallow it, but like that's how python inheritance works). If I had to choose, I'd rather it be more like the original proposal, but my vote is still for one contract one file.
Additionally, if we assume one contract one file I personally don't like the self.var syntax at all. I don't think it is necessary because it is usually pretty clear what is a temporary variable passed as an argument (or created in a method), and therefore what is part of contract storage. From a pure aesthetic point of view, those are 5 extra characters you have to write everywhere and it drives me nuts because I don't think there's that much benefit in clarity. This opinion only holds if it's strictly one contract one file though, I think self is more useful to keep distinctions if multiple contracts are allowed.
@federicobond the GO composition idea is intriguing. I don't know much about GO, but alternative examples to inheritance are very useful. From what I could tell with the example given in the GO documentation, it's more talking about module interfaces and types. I will keep reading about it for parallels with contracts (I think of contracts as a data storage spec and the rules to manipulate it)
I suggest you take a close look at the concept of embedding in Go. There are some interesting ideas there to provide encapsulation and separation of concerns without inheritance.
One contract per file is a feature, not a bug. I'm severely against introducing _any_ form of composition at the Viper language level, and having experience with both the Java and .NET ecosystem, it is my conviction that one class (contract) per file is an _extremely_ important readability feature.
Closing, if someone feels strongly about this consider open a new VIP. But also consider that we are in the process of introducing really useful interfaces (in part #885), whilst sticking to one contract per file.
Most helpful comment
Closing, if someone feels strongly about this consider open a new VIP. But also consider that we are in the process of introducing really useful interfaces (in part #885), whilst sticking to one contract per file.