glow
glow copied to clipboard
Collect user fees
In GitLab by @fahree on Mar 22, 2021, 23:46
In GitLab by @fahree on Mar 22, 2021, 23:58
changed title from C{-reate contracts that gath-}er fees to C{+ollect us+}er fees
In GitLab by @fahree on Mar 22, 2021, 23:58
changed the description
In GitLab by @fahree on Mar 22, 2021, 23:58
- [ ] Use our CREATE2 proxy to create contracts. This makes it easier to detect contract creation, until other people start using it.
- [ ] Create one contract per DApp in the standard library, now with a deterministic address thanks to the use of CREATE2 above. This also makes gathering statistics easy.
- [ ] Store the state of each invocation of a DApp based on the hash of the initial state for the invocation, which may include a mutually-random nonce to allow for multiple interactions between the same participants, and MUST include the list of participants (of which the creator must be part), so no one can step on other people's contracts.
- [ ] Estimate the savings from not having to create their own contract at every interaction, and have dapp participants deposit some of those savings into the contract as extra fees.
- [ ] Have a "skimmer" API call that allows us to collect the fees, by collecting the difference between what's actually in the contract and what's assigned to users, as stored in some global persistent address that depends on the asset at stake, e.g. 0 for ETH, the address for an ERC20, some hash for other things, etc.
- [ ] Collect usage statistics: number of calls, fees collected, etc.
Before:
- 1 contract per interaction.
- PRO: in mukn/ethereum/evm-runtime, simply store merkleized state at 0 SSTORE
- PRO: accounting of total assets in contract done implicitly by EVM, no cheating possible on that
- PRO: the first interaction step is fully done off-chain
- CON: you must pay for a contract at every interaction -- expensive on ETH due to high GAS price
- CON: not affordable to collect small fees for each interaction
- In mukn/glow/runtime/participant-runtime, first interaction is handled by publish > deploy-contract > prepare-create-contract-transaction
After?
- contract for all interactions using the same dapp.
- CON: in mukn/ethereum/evm-runtime, store merkleized state at
SSTORE - CON: contract must do the accounting of total assets in each interaction, 1 bug and ouch!
- CON: the first interaction, like every interaction, must precisely do the accounting part
- PRO: you don't need to pay for creating the contract every time
- PRO: mukn can collect fees!
- CON: the plumbing may be expensive
- CON: in mukn/ethereum/evm-runtime, store merkleized state at
- In mukn/glow/runtime/participant-runtime, first interaction is handled by publish > deploy-contract > prepare-create-contract-transaction
What does the plumbing need to do?
-
Something similar to what's in my StateChannel.sol (see issue #112)
- account for the assets of each class (native, ERC20, ERC721, etc.) owned by each participant.
- only delegate to application-specific code what needs to be, that can't have any side-effect (maybe use STATICCALL if user-specified extension required; or our own bytecode interpreter)
- actual modifications go outside the code being STATICCALLed, by the general-purpose plumbing around
-
assets deposited to an interaction can be divided in 3 kinds:
- fully owned by a participant ==> shared by all interactions on a given "contract", for each participant
- deposited as collateral on a contract by a participant ==> separate by contract(?), for each particitpant and for each contract
- at stake in a given contract with undetermined ultimate owner, right now algorithmically controlled. ==> for each contract.
- in a given contract, forfeited, to be divided between participants (and operators?)
-
When there are only two participants, there's an easier solution to asset accounting: everything in the interaction is forfeited by whoever stops cooperating. (But riskier in case of successful DoS attack of one party).
-
Skimming: maintain a total of all assets under control of interactions
- whatever is above that, the Operator (i.e. MuKn) can "skim".
- And at every transaction, while maintaining this total, reserve a small part for later skimming.
-
Updating code
- Update the contract: enforce the above instead of having a 1 contract-per-interaction
- Update the client: use the above
- At the first transaction in an interaction, we must call the contract with some special value to tell it "this is new", and it will allocate a new interaction ID
- The interaction ID MUST be a hash of some data that includes the participant addresses at well-determined locations, so other participants cannot possibly fake it.
- The contract MUST update the global amounts under management.
- The contract MUST maintain a per-interaction ledger
- Any bit of State CAN be either stored at a safe merkleized SSTORE location based on the interaction ID (but also possibly, e.g. participant and asset), OR it can be merkleized as a component of a hash stored in an above stored location.
- Transactions AFTER the first reuse the same ID as the first time around
Bonus:
- if we can share code between state-channel contracts and regular contracts.
- if we abstract away the message-publishing code so the same code can be used everywhere that doesn't depend on the message-publishing backend, so we isolate what does or doesn't depend on the current interaction being in a state channel or direct style or optimistic side-chain, etc.
In GitLab by @fahree on Mar 24, 2021, 04:21
marked this issue as related to #145
In GitLab by @ApolloUnicorn on Mar 24, 2021, 04:29
@isd
In GitLab by @fahree on Apr 1, 2021, 00:44
assigned to @Akaa
In GitLab by @ApolloUnicorn on May 10, 2021, 23:05
unassigned @Akaa
In GitLab by @ApolloUnicorn on May 12, 2021, 04:28
assigned to @isd
In GitLab by @isd on May 16, 2021, 05:20
Question: is the CREATE2 proxy you refer to something that we already have, or is it something we will have to build/set up?
Per our earlier discussions, this issue should be broken up into smaller tasks. Some possible sub-tasks that I see, some of which correspond to the checkboxes above (each top-level bullet could be its own task):
- Using the
CREATE2
proxy sounds like a nice self-contained task, though see my question above. - Support for multiplexing interactions on a single evm contract:
- Verify/ensure that we aren't relying on the VM's balance for anything, and are doing relevant checks against our own global variables. Necessary since if we're multiplexing multiple interactions, the balance will be a total, not the balance for the current interaction.
- Adjust the startup code such that we can specify a parameter for which merkelized state to restore -- instead of fetching the digest for the state from storage address 0, we'll hash the parameter to find the address.
- Skimming support
- Track (in a variable outside of the merkelized state) funds available for skimming.
- When a new interaction is spawned on the contract, expect some additional funds in the deposit and add them to the skim balance.
- Provide a way to tell the contract to transfer funds from the skim balance to the contract creator's account.
Gathering statistics is already its own ticket (#145)
Does all this sound reasonable?
In GitLab by @isd on May 16, 2021, 05:33
This also might provide useful context in which to implement the global variable support that I punted on in !133; @fahree and I both agreed it made sense to wait until we had something that need it, but that doesn't necessarily have to be the multiple-assets code, and in fact I think I'm going to want this right away as I work on the multiplexing support.
So I think step 1 is to make sure the existing global variables which are per-interaction are allocated inside the merkelized state, rather than with define-consecutive-addresses -- so doing the punted part of the scratch space issue, basically. I think on master this is just deposit
and timer-start
. Getting that small piece merged will allow us to avoid large merge conflicts with multiple assets.
In GitLab by @Akaa on May 16, 2021, 14:32
You have a code that creates contract using CREATE2(EVM Opcode) It should have Interaction ID for say a session. Here, some group may want to settle online e-commerce transaction or game or betting activities. You create an interaction ID and a contract(CREATE2). This is done by one of the participants. The new contract address would be what other participants would use going forward. Contract ADDRESS should be created off-line.
Subsequent interactions by the members of this specific session(group) would be tracked by their Interaction ID. The contract created with CREATE2 would not hold state. Its storage would be routed back to the main code that created it. Here you see the need to have a way to know which CREATE2 contracts is updating state, that's the need of interaction ID(one of them). You can then throw a lot to the storage depending on your design. Even after the CREATE2 contract has closed(self-destroyed), we still have their records.
By having global state variables for Dapps interacting with the contracts we have saved them the cost of creating storage each time they want to close out transaction on-chain. We intend to make some money from this enough. That is part of the fees we collect.
In GitLab by @isd on May 17, 2021, 05:08
Actually, looking back at evm-runtime, I think I'm wrong re: globals: we don't need to support general globals, we can move the existing globals that we want to be per-interaction inside the merkelized state trivially, just by putting them after frame@
-- and timer-start
is already there.
Intuitively I think brk
will also need to go inside the merkelized state, which probably means shuffling around the frame restoration logic, but we can fudge it for now if we choose since we're not actually doing dynamic memory allocation (and I think the logic may be wrong/unimplemented as is, not 100% sure).
In GitLab by @isd on May 17, 2021, 06:11
Looks like I am further mistaken, in that deposit
doesn't actually need to be merkelized, since it's an ephemeral variable that doesn't outlive the call.
Re: verifying we aren't relying on the VM's balance, the only place I see this used is in the &SELFDESTRUCT
helper -- which brings up the fact that we also don't want to ever actually use a SELFDESTRUCT
instruction if we're multiplexing contracts, so probably we want to make the debug
version the only one, or at least make the condition debug OR multiplexed.
Fixing the balance accounting will be trivial.
In GitLab by @isd on May 17, 2021, 06:19
@Akaa, I'm clear on what the use of the CREATE2 opcode is for, what's unclear to me is what exactly @fahree means by "proxy" -- is this a running smart contract that we'll deploy, is it a separate daemon outside the blockchain? Is it something we'll have to implement or is this something that already exists that we just need to hook up to the rest of our code?
In GitLab by @isd on May 17, 2021, 06:39
I'm going to open separate issues for sub-tasks as I identify them, #185 being the first.
In GitLab by @Akaa on May 17, 2021, 07:23
A proxy (from my understanding) is a contract to be shared by all Dapps that intend to close on-chain transactions. Here, you have two contracts. Once which is shared by all the Dapps. But each Dapp whenever it starts a new interaction creates the second contract via CREATE2. However, this second contract should not have its own storage. Its storage should be routed back to the shared(proxy) contract. Proxy contract uses interaction ID to track each contract and it states. The contract created by CREATE2 is almost like multisig (channel) contract.
Everything is on-chain except creation of contract address.
In GitLab by @Akaa on May 17, 2021, 19:21
You don't need deposit
. Use CALLDATA, get everything from it.
The proxy contract should be like state machine:
- Start closing(Create all the states, and they would be indexed with interaction ID).
- Closing in progress
- Error in data protocol
- Other stuff You may combine (1) and (2) into ... Build data protocol that would come out of CALLDATA. Use the header to decide which state to execute.
Transaction contract
Just create something that can do multisig and validation with payout. (Pump changes back to proxy)
Someone can call Transaction contract without going through proxy, but any state change is stored at proxy contract.
In GitLab by @fahree on May 17, 2021, 23:35
-
I wrote several "simple apps" in EVM assembly in
simple-apps.ss
, including the trivial CREATE2 wrapper, a batching proxy, etc. -
I wrote in
meta-create2.ss
some infrastructure so the CREATE2 wrapper can have the very same address on every EVM network, making contract addresses universal.
I believe that contract from meta-create2 is the one we should be using and following.
In GitLab by @fahree on May 17, 2021, 23:37
Note that deposit
is a short-lived variable, only valid during the transaction. It should not be part of the persistent merkleized state. timer-start
should, and already is.
In GitLab by @fahree on May 17, 2021, 23:41
And indeed, we don't want any SELFDESTRUCT
in a shared contract, only a variant that would account for the current interaction's balance.
(Also, unless the contract is limited to ERC20s trusted by everybody, we need some protection against untrusted ERC20s; at the minimum, some variable that guards against reentrancy during the commit block that makes transfer CALLs.)
In GitLab by @fahree on May 17, 2021, 23:44
@Akaa I admit I don't understand everything you say :-(
@isd If that helps, you can create a Google doc about what needs to be done, that we can comment on.
In GitLab by @alex413 on May 18, 2021, 03:36
@fare What's the rough deliverable date for this issue?
In GitLab by @isd on May 18, 2021, 04:44
Note that
deposit
is a short-lived variable, only valid during the transaction. It should not be part of the persistent merkleized state.timer-start
should, and already is.
Yeah, I realized this at some point after my first comment, see comment starting with "Looks like I was further mistaken"
@alex413, I think it's a bit too soon to make specific predictions about the overall issue; we need to pin down some details first. Though see my comment on #185.
I made an etherpad doc, contents based on an earlier comment of mine, we can work there:
https://thelibrary.sandcats.io/shared/tbDx2TPtfQ1IAzKsIHm_9CLkROkg1xxeQww8oYvmyyR
In GitLab by @isd on May 18, 2021, 05:04
I'm still fuzzy on exactly what the proxy is for; my current understanding is that it is itself a contract that will be invoked to create other contracts, and that somehow this indirection will make gathering statistics easier. But if we're already going to be multiplexing interactions of the same dapp onto one contract, I don't know that I understand why this helps?
In GitLab by @isd on May 18, 2021, 05:07
Another subtask that needs to happen to multiplex contracts: right now the addresses for the participants are compiled into the contract itself as constants at e.g. the set-participant call sites. In order to use the same compiler output for multiple interactions, we would need to rework this so that these are supplied when spawning an interaction, and resolved at runtime.
In GitLab by @fahree on May 19, 2021, 08:50
If that's what you call the "proxy", the CREATE2 wrapper exists so anyone can create a contract with a fixed address based only on the contract initialization code (plus some agreed-upon salt), so people can use MAST techniques on Ethereum, wherein the agreed upon code is only revealed if needed in case of dispute, whichever the party starting the dispute may be. In the case of shared contracts, that also makes it trivial to determine if the contract was already created: just use eth_getCode
on the expected contract address.
(By contrast, contracts created using CREATE depend on the nonce of the creating user, which makes it impossible to detect whether a contract of given content was already created, short of scanning each and every contract ever created on the blockchain.)
Since for now presumably we'll be the only one using this wrapper contract, we can also use it for gathering usage statistics on all contracts created through our infrastructure.
In GitLab by @isd on May 23, 2021, 06:10
Another subtask that needs to happen to multiplex contracts: right now the addresses for the participants are compiled into the contract itself as constants at e.g. the set-participant call sites. In order to use the same compiler output for multiple interactions, we would need to rework this so that these are supplied when spawning an interaction, and resolved at runtime.
Actually, I think these are already stored in variables, so that's fine, no change needed.
In GitLab by @isd on May 23, 2021, 06:14
So as I understand it, the point of the CREATE2 wrapper is just so that we can be notified when contracts are created using glow. Is that basic statement of purpose correct & complete, or is there more to it than that?
In GitLab by @isd on May 23, 2021, 06:18
Minor design point, I think it would simplify things if instead of storing the state digest, we stored the state digest XOR'd with the initial state digest -- this way we don't have to have any kind of explicit "creation" logic for interactions; when the shared contract is created all possible interactions implicitly exist and are in their initial state, since state1 xor state1 = 0
In GitLab by @isd on May 29, 2021, 02:04
A caveat to my xor
suggestion is, we need to make sure the common state is valid; if participants can specify an arbitrary hash for initial state, they could possibly provide a bogus state that allows them to extract funds that belong to other interactions on the same contract.
So to make that work we probably need to have some split between per-interaction inputs and "static" initial state common to all interactions, and have the interaction prelude verify that the "static" state matches a fixed hash.
So we need to tease apart variables based on which section they belong in.
@fahree, interested in your thoughts (also to my question re: CREATE2).