foreign
[Music]
then I worked with Aragon for a bit and
now I'm working with synthetics
mainly addressing the problem of how do
you build a super complex smart contract
system in a way that you can iterate
through it and fix bugs and and improve
an experiment
um and other than that I I consider
myself a bit of a an educator in the
space I just it's not that I know a lot
of things I I just love to empower other
people with with knowledge right so what
we're gonna do today
is talk about proxies
uh and then like in general and then
we're gonna talk about a pretty
sophisticated uh type of proxy that
we're using in synthetics
so
I think that it's critical for for
everyone to understand how proxies work
under the hood I think that it's not
okay
for a Dev to use proxies and not know
how they work
but the good news is it's that they're
really easy like in the end there's like
no mystery it's very easy to demystify
it right so I I insist uh besides the
proxy I'm going to show I think the the
essence of this talk is to
to promote the awareness of how proxies
work and if you're going to use one like
make sure you understand how they work
so to illustrate this we're going to
play them like we're going to start with
a with a very simple contract and make
it upgradable and see what happens
so this is the contract I don't know if
it's a good idea to bring in code to a
talk but I don't know if you can see it
but it's just a contract that sets a
value right it has a function set value
you can set the value and it records who
said the value it's message sender and
an image an event that's it
so this car this contract is deployed at
the 0x1 address right
and then
after deploying it Bob calls uh set
value 42 right
then
42 is set in storage slot zero because
the variable is declared it's the first
variable declared
and
uh zero X Bob which is the address of
Bob uh gets stored in the second slot
which is slot one okay
that's how solidity lays out storage
automatically when you declare variables
in a contract
and an event is emitted from 0x1
now they decide to make their contact
upgradable all right how does this work
they they decide to deploy a proxy which
is just a function a contract that has a
function you can tell it what the
current implementation is
which is going to be the other contract
and then it has a like a magic assembly
function which forwards anything using
call to the implementation contract
so this is deployed at 0x2 then Bob
calls set implementation 0x1
and in the proxy storage it's not zero
now holds 0x1 because the address
implementation variable is declared in
slot it's the first one declared so it's
saved in slot zero
then
they're now connected this is the proxy
and this is implementation and block
calls value right
it forwards the call to that uh implicit
getter that solid generates
and ever since return 32 which is
expected right
yeah
now uh Bob called set value one three
three seven let's see what happens uh it
gets forward using call
to the set value function in the
implementation
and
it affects the storage of the
implementation not of the proxy
and its stores
one three three seven in slot zero and
0x2 installed uh one
that's weird right that's message sender
so
the
the problem that we have here is that
call makes the
execution context to be the
implementation of the proxy like the
event is submitted from the
from the implementation which is also
not a good thing right because you don't
want to have a protocol and and tell
people like to be changing addresses
every time you update the implementation
right so
the problem that we have with this
particular proxy that uses call is that
the execution context is here right and
we don't want that
so what is an execution context is when
you run code basically what determines
uh which storage uh space to use uh who
message sender is
and where emits come out from right
um
there's more to it but that's like
pretty much it
so how can we take the proxy the
execution context to the proxy we just
need to use delegate call
so call wants the code that we're
running in the current context and
delegate call runs the code in the
context of the caller right so here we
have a second proxy right which is the
only difference is that it uses delicate
call right
it's deployed at zero three zero X3
it's connected
to the implementation the same one as
before and now when Bob calls set value
one three three seven
it uses delay core so the execution
context is this right
we are using the storage space of the
proxy which is good
the event is coming from the proxy which
is good
uh now Bob calls value like the getter
right
the execution context is still that it's
fine now
this is gonna delegate call to whatever
is stored in the implementation and the
implementation is in slot zero and the
value now holds one three three seven
right so what are we delegating call to
to some contract at one three three
seven which there's probably nothing
there right so we just we have a storage
Collision right
we overwrote the address of the
implementation
with a number right so we basically
bricked this proxy right so daily call
is awesome but it is dangerous because
you have storage collisions
so to solve this
Bob goes to the next level and and these
structures the proxy storage so what is
this structuring it's basically
choosing where to put to put to store
something that's it it's not using
solitaire's custom slots but just
choosing where you put it
um
so for solidity first variable is it
zero second at one Etc and you have
infinite slots
destruction is just picking a custom
slot right so we have the third proxy
here is called unstructured proxy and
the difference is that we are not using
solidities like regular storage slots
but we are
declaring
where we store things at slot 1000 right
and using that the the code looks a
little bit weirder but that's it
so we deploy this proxies he works for
we connect it to the implementation oh
this is important can you see the
storage it's
the implementation address is stored at
the custom slot of 1000.
so that's destructured right now
so now we call set value
it makes a delegate call
the execution context is that
we write the new value values but they
don't like step over let's say the
implementation address
right and the event comes from from the
proxy which is fine
so we have a proxy that works right
and
now we can upgrade the implementation
because we we know that it works so we
have value holder V2 the only difference
is that we added a new variable called
dates right just added it on top
uh and just whenever someone sets the
value we also record record when that
happened right
so now we connect the implementation we
call Value right
a delegate calls
and value is whatever is stored at slot
one right according to solidity storage
layout
and at one we have coax pop so
another problem
um
this is another type of storage
Collision we shifted the the
implementation storage uh and we have a
collision between versions of the
implementation right we have income
incompatible storage layout
so Paul understands
that to to avoid this you in an
implementation you you only append to
the storage instead of like putting it
anywhere right so keep uh moves the date
variable to the end of the previous
storage layout
um and that's it that's pretty a pretty
simple fix that's another rule of like
using proxies always a pen to
um and now
this value is gonna get whatever is
stored at slot zero
which is one three three seven so it's
fine
so
we we avoided that Collision
so storage collisions it's it's critical
to understand when they occur and it's
basically the two types of collisions
that I just showed you that's it if you
get that you you can pretty much think
about any type of coalition
things to consider the execution context
is always the proxy so everything is
stored in the proxy
um there's two type of collisions that
we just talked about
um the first kind can be avoided by
unstructuring
the storage layout and
the the second one can be avoided by
just making sure that the uh updates to
the storage layout and implementations
is valid
always append
and something to consider and this is
critical multiple inheritance
um flattens your contracts so you cannot
protect the storage layout so you can
add a new inherited contract to you to
your like super contract and it can add
like five new variables in an
unpredicted uh part of the layout uh
part of the layout
so
it becomes hard to detect when you have
invalid storage mutations and you need
to use tooling if you use proxies I see
a lot of people using proxies and not
using tooling and
it's not a good idea
and even if you detect them and this is
not something that tooling fixes
it can be very hard hard to avoid the
invalid mutation like if you have a
inherited contract and it it causes this
Collision to avoid it you you have to do
some weird stuff right so
why not use that technique of
unstructuring on everything not just the
proxy storage but the the implementation
storage definitions
so here we have value holder before the
implementation B4 and the difference is
that it uses the same technique that the
proxy use and stores everything at slot
5000 right everything else is the same
so we deploy this we connect it to our
proxy which we're not changing anymore
because it works
um
we call set value right it makes it
delegate call to the implementation
and we can see that we have the gray
storage we're not using anymore
we have the proxy
name storage name space let's say there
and the new implementation namespace uh
here and they don't Collide right
so we've unstructured the implementation
storage right now
we have we're pretty happy with this
proxy like configuration we
the the context is kept at the proxy and
collisions are avoided using
unstructured storage or storage
namespace absolutely everywhere
tooling should still be used to
guarantee that there's like no uh
storage Collision that you don't notice
but the thing is that uh this like
custom or manual use of storage uh makes
um
storage layouts much easier to control
so
now that we understand these like basic
principles of of proxies let's talk
about like what a multi-contract system
looks like in in solidity
so
there's no ideal
standard solution for multi-contract
systems people often use registry which
is basically a contract that knows every
other contract
right and whenever a contract a needs to
talk to contract B it needs to go to the
registry and ask hey I want to talk to
business B here's B and then it makes a
call to contract B right and then B
needs if it's a sensitive operation
needs to
say okay who's calling me a is a from
the system it asks the registry the
registry goes yes
and then okay then you can perform this
it's complicated and it gets it gets uh
messy pretty fast so let's let's try a
pretty crazy solution which is uh we're
calling it the router proxy
um
so we basically have a new contract
which is another contract that has one
variable
it's called cool value and it's also
using these uh this storage
name space system instead of solidities
like own a storage layout thingy but
that's it it just records a variable
right gets the store sets the the stores
value and then it's a an event
so we deploy it at 0x8
and then this is the tricky part bear
with me with this part uh Bob uses
tooling to build a router right so this
is basically a table right
it has the addresses of the two
contracts uh value holder and another
contract hard-coded
and it's
a fallback function
basically
has to do like this binary search
algorithm
to determine which implementation has
that function right is it valueholder or
is it another contract right and it just
checks the incoming selector and
forwards it to the appropriate
implementation and that's it and then it
just makes the the regular delegate call
uh proxy forwarding
so
this is deployed at 2x9
and then we set the router as the
proxies implementation
right 0x9 is the new implementation and
now we have this we have the proxy over
here
we have the router over here and we have
the implementations over here right so
when Bob called said value seven you
don't I don't think you guys see it but
it's it's calling set value seven with
the number seven
it makes it really a call to the to the
router
and then another daily call to the to
the another contact implementation
because the router figures out that the
the function set values in that contract
let's keep in mind that the execution
context is still the proxy it doesn't
matter how many deleg calls you make
it will always be the the the entry
point
so
that works like it sets uh the the other
contracts Uh custom slot what was 9 000
so it's store seven right there
and the event is still emitted from the
the execution context
so
if Bob wants to upgrade the system all
he needs to do like here we're making a
like a silly change right we're just
multiplying the incoming value just by
seven
um
this new another contract B2 is deployed
at 2x10
a new router is generated by the tooling
it just has the only difference is up
there
in in another contracts has a new
address
everything else is the same
and Bob sets the implementation of the
proxy to that new router
so
that's how you upgrade the any contract
in your system so what would a more
complex system look like
maybe like this you have the main proxy
right you have the different storage
name spaces of that proxy
and then you have the router which you
keep changing every time you upgrade the
system and you have the different
modules that specify uh a particular
behavior of your system
and then
you have this thing which is really cool
cool because uh called mixense because
it allows intermodular Communication in
a way that we we're going to see that's
really efficient and really easy
um
so yeah we don't use any storage other
than the execution context storage
gas efficiency
uh it's like a concern with this pattern
because you're used doing two delay
calls
um
keep in mind that transparent proxy is
the ones almost everyone uses uh costs
like 3 000 gas
Universal proxies about 1600 and this
system uh uses only uh 2 600 gas which
is all right
and then intermodial intermodular
Communications how would a module talk
to another module you could
you could cast your module as the other
module and just call its function
because every module is this the system
but the problem with this is that
message sender would be lost because
it's it's a call you you break the
delegate call chain right
so you need to delay call to the other
module just the the same like
self-casting mechanism but with delay
code uh it works but there's something
much better which is mixins
which are pieces of code that know how
to interact with another module storage
they're like delegates for that
particular part of the logic right and
they're not deployed they're just
inherited it's like modular inherits a
bit of the code of more UB
and the nice thing is that you can tell
the mix in to interact with the other
module without even making a call
so communication becomes super cheap
let's let's see an example we have owner
storage which just declares a struct
with what single variable uh that
mechanism to to get custom storage slots
then we have the owner mixing
which knows that storage and only has
and only only owner modifier right that
does the typical check right
and then in owner module we inherit the
mixing
which gives us the only owner modifier
access right
and we have a getter for the owner
and now we have a new version of value
holder B5 right that has a single change
which is we're using the only owner
modifier here from another module using
the owner mixing
if you want to use this you only have to
change your code style a little bit it's
kind of weird but you get used to it
fast because it's simple
you just need to use storage name spaces
instead instead of like regular
variables
and yeah you get used to it
should Solitaire do this under the hood
there's a proposal from maxim4
um
so it is something that the solidity
team is considering uh this could be a
language supported feature to have like
a contract hub
and yeah why use the router
you don't have a contract size
limitations anymore because you can just
combine like I think I I tested it once
with like 800 functions which is pretty
crazy
um it's like the router merges all the
contracts into a single contract so
then as we just saw we have good easy
Communications between the models
without having to use a registry or
authentication or anything
it's ideal for complex experimental
systems
and the other nice thing is that the
router is since the addresses are hard
coded it's very explicit so it's good
for
for governance if you want to make an
update to the system you show your
community like this is what we're going
to change this is what the configuration
of the system will look like it's not
hidden in some
uh Dynamic storage somewhere it's right
there
uh and yeah that's why we're using it as
a core component of synthetics B3
because it's it's a complex system that
needs to not have all the complications
of intermodular communications and all
that
and well if you want to try it out it's
a heart health plugin it's uh
synthetics B3 uh hard hat router
it generates the router source
it manages storage namespaces for you it
performs validations to ensure that
there's no there are no storage
collisions and that's pretty much it
that's what the plugin does
so thank you very much
thank you
um
are there any questions
I just want to know if I'm missing
something but this proxy router could be
the same as the
multi-facet proxy you know the diamond
proxy but with the hard-codile
uh implementations uh yes I I couldn't
hear it perfectly but I think you are
asking about the diamond proxy right
yeah yeah yeah yeah this is this is
based on the diamond proxy so is that
that the diamond proxy but without
getting the implementation from the
storage yeah but instead hardcoding them
yeah basically okay it's it's some
people are calling the diamond proxy a
dynamic router and this one is static
router and we like this one because uh
for our project because it it saves
storage reads because the the values are
hard-coded and it's also more explicit
like that what we don't like about
diamonds is that you don't like if
you're a Community member or whatever
and you want to know what's the current
composition of the system you need to
query it a lot right
but yeah it's the same otherwise just
one question
so you only use the sort of
ins and addresses and stuff what happens
with mappings and arrays in regards to
storage Collision so can you repeat a
bit louder so if you have an array yeah
does that affect storage Collision
well if if you declare your array or any
Dynamic type inside of those storage
namespace drugs then solidity is like
regular storage layouts
um system is used which uh uses
unstructured storage under the hood like
if you have a dynamic array the position
of that array I think it's going to be
it's slot say if it's
9003 because it's the third variable
instruct uh the hash of that so it's
going to be some other random place so
it probabilistically the even though
they're in in struts so some things are
going to be spread out but the the
probability of a collision is uh very
low like insignificant
so you mentioned a couple times that we
should use Tooling in order to check for
storage collisions what sort of tooling
do you recommend contract developers use
sorry I I can't hear really really well
from here I'm just going to stand there
okay so in your talk you mentioned a
couple times that you should be using
Tooling in order to detect storage
collisions what sort of tooling should
developers use in order to do these
checks
all sort of tools okay
um
yeah so if you're using open settlements
proxies you should use their tooling
if you're using uh the router
as you can see we just we didn't just
offer like a solution to generate the
code but we have we have a it checks
your the storage layout of your entire
project so in that case you can use our
code right so I would say always use the
tooling of whoever is providing you the
code the smart contract code of the
proxy were there any attempts to solve
this garage Collision thing on the evm
or compiler level like to sandbox each
contact for example
I don't think there's a need to
um
the way uh solidity like these
structures arrays and mappings and all
that it's it's
theoretically impossible to to get a
collision so there's no need to sandbox
it the problem with collisions is when
people use like a design that's not
supported at a language or at a protocol
layer like the evm right
and they get collisions between two
contracts right
um
so so I don't I'm not aware of any
attempt at that level to avoid
coalitions is it possible to migrate and
open Zeppelin proxy to this one the
router proxy or would we have to start
from scratch code wise it's pretty much
the same like you can use a new
Universal proxy as your entry point so
you could probably just use that and the
migration
is the the crazy thing about this is
that the the router routing occurs at
the implementation right so code wise
you're okay
then storage wise right you just need to
probably
choose new namespaces and populate the
data right or accept that your modules
are going to use existing search right
and make sure that new modules declare
like a new namespace or something but
yeah sure you can do it even if so if if
solidity like makes this a language
feature
you just stop using generate routers and
deploy a solution Hub right so it's
completely future proof I think
how standardized it is is proxy are you
are you the only ones using it or is
someone else using it in production
doesn't mean first question and the
second question is uh do you think it
will be useful to have something in the
uh like a public function in the in the
proxy to share the signatures that are
being used so let's say a user or
someone that wants to check what is
being used doesn't have to dive into the
source code
yeah so standards not many right now if
you deploy this you won't see anything
on etherscan for example etherscan
doesn't know how to interpret a proxy
that has multiple implementations
which is unfortunate but we're trying to
solve that pretty fast it shouldn't be
hard
um and your second question
um
you would just add a module that adopts
a ERC 165 is it
that uh just like
replies it it has that function I don't
remember the name of the function that
gives you the entire interface of the
whole system okay that's it thank you
very much