You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
for each registered inner write call - add a tag in the resulting interaction transaction: 'interactWrite': 'calleeTxId'
Eval state of the callee contract:
load all interactions (both "traditional" and the one with "interactWrite": "calleeTxId")
sort the transactions
for all the "interactWrite" transactions - load the calling contract and eval its state up to the given "interactWrite" transaction height - this effectively should cause the "innerWrite" to be called - and with the help of the state cache for the callee contract - update its current value.
Detailed description
Introduction
SmartWeave protocol currently natively does not support writes between contract - contracts can only read each others' state. This lack of interoperability is a big limitation for real-life applications - especially if you want to implement features like staking/vesting, disputes - or even a standard approve/transferFrom flow from ERC-20 tokens.
This is a great and innovative idea that greatly enhances contracts usability, but we've identified some issues:
Contract developers need to add FCP-specific code in the smart contract code and in its state. This causes the protocol code to be mixed with the contract's business logic. Additionally - any update or change in the protocol would probably require the code (and/or state) of all of the contracts that are utilizing the FCP, to be upgraded.
In order to create a "write" operation between FCP-compatible contracts, users need to create two separate transactions:
invoke operation on Contract B to add entry in the foreignCalls state field of the Contract B
readOutbox operation on Contract A, that underneath reads Contract's B foreignCalls and "manually" calls Contract'a A handle function for each registered 'foreignCall'
We believe that writes between contracts should be implemented on the protocol level (ie. contract source code and its state should not contain any technical details of the internal calls) and that performing a write should not require creating multiple interactions.
Solution
Attach a new method to the SmartWeave global object (the one that is accessible from the contract's code) with signature: function write<Input = unknown>(contractId: String, input: Input): Promise<InteractionResult>
This method allows to perform writes on other contracts.
The caller of such call is always set to the txId of the calling contract - this allows the callee contract to decide whether call should be allowed.
The method first evaluates the target (ie. specified by the contractTxId parameter) contract's state up to the "current" block height (ie. block height of the interaction that is calling the write method) and then applies the input (specified as the 2nd. parameter of the write method). The result is memoized in cache.
This has been implemented in the Contract.dryWriteFromTx() and ContractHandlerApi.assignWrite()
For each newly created interaction with given contract - perform a dry run and analyze the call report of the dry-run (feature introduced in feat: generate 'stacktrace' from all the contract interactions #21). This should generate a list of all inner-calls between contracts.
For each generated inner call - generate additional tag: {'interactWrite': contractTxId}- where contractTxId is the callee contract.
This has been implemented in the Contract.writeInteraction and InnerWritesEvaluator.eval().
For each state evaluation for a given contract ("Contract A"):
load all "direct" interactions with the contract
load all "internalWrite" interactions with the contract (search using the tag introduced in point 4)
concat both type of transactions and sort them according to protocol specification (i.e. using the sortKey)
for each interaction:
if it is a "direct" interaction - evaluate it according to current protocol specification
if it is an "internalWrite" interaction - load the contract specified in the "internalWrite" ("Contract B") tag and evaluate its state. This will cause the write (described in point 1.) method to be called. After evaluating the "Contract B" contract state - load the latest state of the "Contract A" from cache (it has been updated by the write method) and move to next interaction.
This has been implemented in the DefaultStateEvaluator.doReadState()
This method is also effective for nested writes between contracts.
Alternative solution
An alternative solution has been also considered. Instead of writing only a tag with id of the calling contract (point 3. from the description above), one could write the exact input (ie. function, parameters, etc) of the call. This would have an advantage of the increased performance (as we could evaluate the "Contract A" state without the need of evaluating the "Contract B" state in the process), but would ruin the "lazy-evaluation" idea of the protocol and reduce the safety of the solution - as the Contract A would need to fully trust the calling Contract B - if the Contract B would save some unsafe data in the input - there would be no way to exclude it from the execution.
The text was updated successfully, but these errors were encountered:
Add an option to change a state of a contract from within other contract code.
Proposed solution:
'interactWrite': 'calleeTxId'
Detailed description
Introduction
SmartWeave protocol currently natively does not support writes between contract - contracts can only read each others' state. This lack of interoperability is a big limitation for real-life applications - especially if you want to implement features like staking/vesting, disputes - or even a standard approve/transferFrom flow from ERC-20 tokens.
Some time ago a solution addressing this issue was proposed - Foreign Call Protocol (https://www.notion.so/Foreign-Call-Protocol-Specification-61e221e5118a40b980fcaade35a2a718).
This is a great and innovative idea that greatly enhances contracts usability, but we've identified some issues:
invoke
operation on Contract B to add entry in theforeignCalls
state field of the Contract BreadOutbox
operation on Contract A, that underneath reads Contract's BforeignCalls
and "manually" calls Contract'a Ahandle
function for each registered 'foreignCall'We believe that writes between contracts should be implemented on the protocol level (ie. contract source code and its state should not contain any technical details of the internal calls) and that performing a write should not require creating multiple interactions.
Solution
function write<Input = unknown>(contractId: String, input: Input): Promise<InteractionResult>
This method allows to perform writes on other contracts.
The
caller
of such call is always set to thetxId
of the calling contract - this allows the callee contract to decide whether call should be allowed.The method first evaluates the target (ie. specified by the
contractTxId
parameter) contract's state up to the "current" block height (ie. block height of the interaction that is calling thewrite
method) and then applies the input (specified as the 2nd. parameter of thewrite
method). The result is memoized in cache.This has been implemented in the
Contract.dryWriteFromTx()
andContractHandlerApi.assignWrite()
For each generated inner call - generate additional tag:
{'interactWrite': contractTxId}
- wherecontractTxId
is the callee contract.This has been implemented in the
Contract.writeInteraction
andInnerWritesEvaluator.eval()
.sortKey
)write
(described in point 1.) method to be called. After evaluating the "Contract B" contract state - load the latest state of the "Contract A" from cache (it has been updated by thewrite
method) and move to next interaction.This has been implemented in the
DefaultStateEvaluator.doReadState()
This method is also effective for nested writes between contracts.
Alternative solution
An alternative solution has been also considered. Instead of writing only a tag with id of the calling contract (point 3. from the description above), one could write the exact input (ie. function, parameters, etc) of the call. This would have an advantage of the increased performance (as we could evaluate the "Contract A" state without the need of evaluating the "Contract B" state in the process), but would ruin the "lazy-evaluation" idea of the protocol and reduce the safety of the solution - as the Contract A would need to fully trust the calling Contract B - if the Contract B would save some unsafe data in the input - there would be no way to exclude it from the execution.
The text was updated successfully, but these errors were encountered: