-
Notifications
You must be signed in to change notification settings - Fork 405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make contract addresses predictable ("deterministic") #942
Comments
I would recommend that instead of using a monotonically increasing nonce (counter), it should be using a 32 byte salt, similar to how it is done in One, it allows a contract to "manage" multiple uninstantiated contracts at once and instantiate them out of order. Let's say a contract wants to create 2 "deposit addresses" for both Alice and Bob to send to. It assigns Alice the first available counter value, and Bob the second available counter value. But if Bob deposits money, but Alice never does, the contract should be able to instantiate Bob's contract without having to instantiate Alice's contract first. Secondly, by being 32 bytes, it can be a hash of precommitted to data. For example, the factory contract can use Edit: in fact, in Essentially, we should be basing off of |
Ethan's approach and Sunny's aproach differ in API compatibility. |
I looked at CREATE2 before and saw that it included both a nonce and initMsg. This may make it hard to precompute a future address. However, we can still keep the same API with this info. It is just the args we already pass (sender, code id, init data) and a sender-specific nonce/counter from storage. However, this may not achieve Sunnys desire of configurable addresses. |
Update, I re-read the CREATE2 spec a bit more carefully:
so, salt is passed in as an argument, not the auto-incrementing nonce that was used in CREATE. This actually makes a lot of sense, but is one more argument to include in the messages It also describes how to handle collisions (using the same salt / initCode):
If we adopt CREATE2-like semantics, we either need to make a new message type (both for contracts and external callers), or (and maybe this is a bad idea), we could leverage the informational "label" field to get a unique salt. Since label is an arbitrary string, one can store a counter ("Subcontract #2") or relevant info ("deposit address for osmos1d2983f39djdfiw") to make it unique. While I can see reservations on repurposing this label field (which we currently only assert to be non-empty), I also have reservations about making completely new paths. I think they both have their pros and cons. |
Using the label field is actually a very good idea. Much better than the layer violation that happens when encoding the user-chosen salt in the inner |
On Ethereum, uploading code and instantiating it is one step. I think it is safe to assume that in CosmWasm it is reasonable to upload a code once well in advance and use the code ID as one of the inputs when calculating the address. |
And if a initiator uses an empty label, would it default to using some incrementing nonce? |
If we calculate the address like this:
we can do that completely stateless. In case the same creator wants to instantiate the same code again, we can error with You instantiated that contract with the given label already, please choose a different label. |
Yeah, I was thinking it would be nice if the creator didn't have to think about the labels. They should be able to if they want to for the examples I provided above, but if they don't want to, defaulting to an incrementing nonce would be a nice to have. But then I realized those people can just use the legacy (current) contract instantiation method. So never mind. |
The point of using labels is that we do not have introduce a second instantiate message type. They would all get the new more powerful and simpler (stateless) address generation. For most users this should not affect anything. Only when one address instantiates a code multiple times they have to use different labels. I don't think this is too much to ask given all the benefits. I'm not a fan of putting different calculations behind the same message type in a case distiction. |
We already enforce label to be non empty and maximum 128 bytes. |
Very nice! Just curious, what's the motivation to add |
Good question. I thought this would work closer to create2:
|
I assumed its for length prefixing the sender address in case a chain can have multiple sender address lengths So like if a chain supports 20 byte addresses and 32 byte addresses, I can't spoof a |
As Sunny said. Whenever you have variable length inputs you either need to length prefix them or add a separator that is guaranteed to not be in the content. On Ethereum, addresses are fixed length (20 bytes). Here we already have 20 byte addresses from pubkeys and 32 byte addresses from contracts. Is there a reason to include the init message? I would not add it. Having to handle a byte-to-byte encoding of the JSON is not that convenient. Your clients might encode the same JSON document differently and all the higher level CosmJS APIs work with objects, not JSON bytes. |
In cosmos/cosmjs#1253 there is a TypeScript implementation including test vectors. Hope they match the Go implementation. |
If we also prefix the label length, we have the option to add an optional variable-length parameter later on in a backwards compatible way. Let's do that. Also Alex prefers to use a 1 byte length encoding, which is enough for the address and the label. So we now have the ADR-028 "Module Account Addresses"
which is the same as
|
Hi, I'm very interested by the subject and would like to put a random question (forgive me if it's a dumb question): If we include the code_id in the hash, how is it supposed to be deterministic? Isn't this a global counter specific to a chain and dependent on its state? If I deploy my code today or tomorrow, the resulting code_id for the same contract will be different? If so, I am then unable to precompute it's supposedly deterministic address? Am I missing something important here? |
@hussein-aitlahcen "deterministic" is not really a helpful term here (please forgive us using it in an unprecise fashion). The address generation has always been determinstic, otherwise nodes had calculated different results. The question is: what are the inputs? Until now, one of the inputs was a global instance counter. An app or develer did not have access to this value in advance, so it could not pre-calculate the address at all. With this change, you can pre-calculate the address as soon as you got the code ID. So your assumtion is corrent, you can't calculate instance addresses for a code that has not yet been stored successfully. However, since in contrast to Ethereum the uploading of code and the instantiation are two steps, we assume that this is a limitation that is not a problem in practice. If you know your code ID tomorrow, start precomuting addresses tomorrow. Do you have a use case for which this is an issue? |
Yes, predictable is probably more accurate.
This is exactly what I was describing: the idea of CREATE2 was to avoid a user having to depend on a chain state to compute an address. If you still include the code ID in the address computation, then you basically void the whole purpose and make it impossible for reproducible/predictable addresses. Here is a use case: I would like to send some tokens to a contract address not yet deployed. Neither the contract code has been uploaded nor an instance of it has been created. Including the code ID in the address defeat this use case as you depend on a global state (current code ID counter on the target chain). Another one: I would like to deploy a set of contract on the testnet, and have a 1:1 between the contract addresses on the testnet and mainnet. Including the code ID defeat this again, because the testnet counter != mainnet counter, right? Another one: I would like to HARDCODE a contract address in another contract, regardless of the target chain, this address must be an address I can "create" via the "CREATE2" equivalent. The CREATE2 EIP describe this as
Instead of having the code ID part of the contract address, perhaps we could just use the code hash instead, the resulting equation would solely depend on the USER itself, no chain state involved:
Using this computation, you realise that you can actually precompute an address, regardless of the target network and it's state. In fact, I would be able to deploy, to the same address, the same contract on all Cosmos chains? |
Good. As far as I can tell there is nothing wrong with changing
which is the same as
where |
@hussein-aitlahcen thanks for all your input. Please note that due to API stability we use the |
It makes perfect sense! |
In #1000 you find a follow-up discussion of this ticket. Would be good if you could have a look and get involved. |
Please also note https://medium.com/cosmwasm/dev-note-3-limitations-of-instantiate2-and-how-to-deal-with-them-a3f946874230 for important updates on Instantiate2 |
This is a request from @sunnya97 from quite some time ago, which I am just getting around to writing down properly.
Request
The desired state is that a contract creator can pre-compute the next 5 (or 500) contract addresses it will create. This will allow them to publish said addresses and send tokens (or vesting tokens) to such addresses before they exist. The original use case was some sort of exchange creating many new addresses for users, then deploying later. However, I have come up with many more use cases:
Current State
The current state is that we create a "sub-module address" for each contract, but the inputs we use are the codeID and a global counter of number of instances created. If someone else deploys a contract, they could claim my address or make the address unreachable. This doesn't allow the creator to have control of the address it will receive.
Ethereum has a
CREATE
opcode that does a "sender + nonce hash" to create the new address. And CREATE2 which does a more complex hash, including initialisation data.Proposed Changes
In order to make these maximally predictable, I would base on the
CREATE
syntax. However, we cannot use the typical nonce, as (a) an external account can instantiate multiple contracts in one transaction / same nonce and (b) in CosmWasm contracts don't have nonces, and we need to count something there.I would add a new counter per-address that maintains how many contracts this sender has created. If a contract creates 3 sub-contracts in the same transaction, each would get another counter value. I would then use (
0xcafe || senderAddress (20-32 bytes) || counter (8 bytes)
) as input into the standard module account hash function.The use of that prefix ensures the has space doesn't overlap with any previously created addresses from the
x/wasm
module. And use of module account derivation means that it doesn't overlap with any addresses created by other modules. Existing contracts already have an address and this will just affect the generation of new addresses without breaking any backwards compatibility.Once this is deployed, we can immediately add support for clients to query the future contract address. With the next CosmWasm upgrade (v1.2?) we can add a new query to pre-compute the address that will be used upon the next instantiate call.
The text was updated successfully, but these errors were encountered: