I am writing a set of interacting smart contracts for a NEAR blockchain. Let's imagine the the following scenario
User sends a token to an exchange smart contract
Token smart contract calls exchange smart contract
Exchange smart contract calls fee smart contract
Exchange smart contract calls another token contract to send back another set token in the trade
Unlike a single shard Ethereum, NEAR does cross contract calls with promises. Whereas a single tripped require() automatically rolls back to the whole Ethereum transactions, in the sharded nature to NEAR smart contracts themselves are responsible for rolling back state changes if the promise they triggered does not complete successfully.
My question is how to safely handle failures in the chain of promises between NEAR smart contracts
What are the failure modes (smart contract function panics, target account does not contain code, out of gas)
How to catch the different errors above and deal with different error modes
Is there already a pattern that allows writing promise chains safely in an easy manner, similar to try {} catch {} in JavaScript await/async model
How I can track between different promises what was the original initiating user transaction that caused the chain of promises to trigger
How smart contracts are forwarding gas and ensuring there is enough gas for the whole chain of promises to complete
Generally you can only tell whether a promise has succeeded without knowing what goes wrong in the case of an error. An example of such a check can be found here https://github.com/near/core-contracts/blob/4f245101d7d029ffb3450c560770db244fc7b3ce/lockup/src/utils.rs#L7. What is the use case of reacting differently to different error that you have in mind?
Related
It seems wNEAR is baked by wrap.near contract, but how does it work?
wrap.near holds w-near contract, which is FT [fungible token] implementation based on NEP-141 standard.
The idea is simple, you send native NEAR tokens and the contract keeps track of the balance of the sender account in its local storage, and there is a reverse operation which allows you to instruct the contract to send NEAR tokens back.
This FT always matches native NEAR token 1:1. "Wrapped" prefix in the name (wrapped NEAR or wNEAR) implies that it just turns native token into FT token. It was created to be compatible with Rainbow Bridge (Aurora) interfaces which allows to transfer FT across NEAR network <> Ethereum network (by locking the tokens in a contract on one network and unlocking the equivalent amount of tokens from a contract that is deployed on the other network).
So getting back to the technical implementation of w-near contract, you can find three core methods:
near_deposit expects NEAR native tokens attached to the function call, and those attached tokens will be deposited to wrap.near account, and in exchange the contract implementation will save a record in the storage that those tokens belong to the account that sent them
near_withdraw deducts the amount of tokens recorded for the account that called this function and sends a transfer of native NEAR tokens back to the caller
ft_transfer (it is implemented by near-sdk-rs helper) virtually transfers the wNEAR FT tokens by updating the records inside the contract storage. NOTE: there is no native transfer involved here, so the transaction will go from tokens sender to wrap.near contract, which will update the balances of the sender and receiver accordingly, and the receiver account will never receive a native NEAR call or NEAR tokens (until he/she calls near_withdraw)
I have been struggling for quite some time with finding an explanation to what events are in Solidity (or in the Blockchain context). As far as I understand, they are a way of storing (or logging) information on the particular contract that can then be updated throughout the life of that contract. But how is this different than a plain ol' variable? Why can't I just create a variable that is then simply updated with new information?
Events in Solidity can be used to log certain events in EVM logs. These are useful when clients are required to be notified of any change or event in the contract. Or maybe in the future you need to search for something that has happened so you go through all the logs. These logs are stored on the blockchain in transaction logs. Logs cannot be accessed from the contracts but are used as a mechanism to notify change of state or the occurrence of an event in the contract. They help us write asynchronous applications.
Events are stored in the logsBloom which is in the header of each block.
Events are piece of data executed on the blockchain and stored in the blockchain but not accessible by any smart contracts. it is kinda console.log in javascript or print in python.
Events are much more gas efficient than using a storage variable
Events are useful for testing the contract. If you interact with oracles, you sometimes want to see if the function call by oracle service is done or not. To see if the function call is done, you emit the result of the function or one of the properties of the result.
Events are useful if you want to maintain the history/log of every change that happens in a mapping.
Deposit contracts were created on the Ethereum 1.0 chain. This kind of smart contract is used for depositing ETH on the beacon chain. An event is emitted every time a deposit is made.
There are two events that must be present in an ERC-20-compliant token:
Transfer : This event must trigger when tokens are transferred, including any zero-value transfers. The event is defined as follows:
event Transfer(address indexed _from, address indexed _to, uint256 _value)
Approval : This event must trigger when a successful call is made to the approve function.
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
You can read this article for deep dive: transaction-receipt-trie-and-logs-simplified
From the docs:
Solidity events give an abstraction on top of the EVM’s logging functionality. Applications can subscribe and listen to these events through the RPC interface of an Ethereum client.
It's easier for an off-chain app to subscribe to new event logs than to a variable change. Especially when the variable is not public.
Same goes for querying historical event logs (easy through the JSON-RPC API and its wrappers such as Web3 or Ethers.js), vs. historical changes of the variable (complicated, would need to query a node for each block and look for the changes proactively).
Example: The ERC-20 token standard defines the Transfer() event. A token contract emits this event each time a transfer (of its tokens) occurs. This allows a blockchain explorer (or any other off-chain app) to react to this event - for example to update their own database of token holders. Without the event, they would have no way (or a very complicated way at least) to learn about the transfer.
Solidity events are pieces of data that are emitted and stored in the blockchain. When you emit an event it creates a log that front-end applications can use to trigger changes in the UI.
It's a cheap form of storage.
You can define an event like this:
event Message(address indexed sender, address indexed recipient, string message);
and emit an event like this:
emit Message(msg.sender, _recipient, "Hello World!");
Read this article for more information and this one to learn how to get events in JavaScript using Ethers.js
Should I pay for every read from NEAR protocol?
How do I view the value stored in NEAR protocol smart contract? (e.g. staking pool fees)
What is the difference between view and change methods?
Should I pay for every read from NEAR protocol?
TL;DR: No, you should not.
In NEAR protocol there are to ways to interact with smart contracts:
Submit a transaction with a FunctionCall action, which will get the specified method executed on the chunk producing nodes and the result will be provable through the blockchain (in terms of near-api-js these are "change methods")
Call query(call_function) JSON RPC method, which will get the specified method executed on the RPC node itself in a read-only environment, and the call will never be recorded/proved through the blockchain (in terms of near-api-js these are "view methods")
You can change the state and chained operations (e.g. cross-contract calls, tokens transfer, or access key addition/deletion) only through the first approach since blockchain expects the user to cover the execution costs, so the user should sign their transaction, and they will get charged for the execution.
Sometimes, you don't need to change the state, instead, you only want to read a value stored on the chain, and paying for it is suboptimal (though if you need to prove that the operation has been made it might still be desirable). In this case, you would prefer the second approach. Calling a method through JSON RPC is free of charge and provides a limited context during the contract execution, but it is enough in some scenarios (e.g. when you want to check what is the staking pool fee, or who is the owner of the contract, etc).
In Ethereum Events are clearly defined, each one is a data structure used to signal some action. In Near, env::log are messages.
Example:
In Ethereum we use logs for most of the token state changes
NEP-21 dosn't say anything about logs.
Is there any point of using logs in near, except "debug" / return user information? Should logs be standarized ?
Maybe it's better to have this discussion in some other place...?
Following on that:
Transaction arguments are serialized and clearly visible. Is there any point to log transaction arguments?
For example: in Ethereum token transfer functions, all arguments are recorded additionally in an event. There are few reasons for that:
With events we should be able to recreate a state of the contract;
it's more readable while browsing a blockchain.
But in case of transfer, I don't think there is any added value, because we don't log anything else than the function arguments.
We haven't added analog of Ethereum events into NEAR yet. Contracts that want to signal about some event need to return it as part of the method result like here. Therefore our env::log is currently for informational purposes only and can be used for debugging and such.
In general, our contracts can choose how to serialize arguments, so it might be useful for the contract to log its own arguments since it might be using a complex serialization mechanism for them.
It might be useful to have logs for complex contracts with complex cross contract calls and callbacks so that it can inform the user how it arrived to a specific result.
NEAR protocol is sharded and transactions between cross-contract calls in shards are final. In some cases, it would make sense to identify longer transaction chains, especially if the promise chain hits the same contract twice (re-entrancy).
Let's imagine a chain of promises: Alice wants to buy a token and the trade is routed through multiple smart contracts and hits one contract in the chain twice.
Alice (user) (tx 1) ->
exchange contract (triggers promise 1) ->
aggregator contract (triggers promise 2) ->
pool contact (triggers promise 3) ->
exchange contract (again)
The exchange contract can identify Alice's account with signer_account_id. However can the exchange contract know about the re-entrancy, namely tx 1 or promise 1, and is it possible to resolve or infer this from the smart contract execution context, so that the exchange would know it is already processing trade for Alice?
Note that Alice may have two chain of promises being processed at the same time, so signer_account_id is not enough uniquely to identify the chain of promises.
I am looking in VMContext and trying to figure out how this is possible.
In the contract environment, you have to pass all the necessary information through arguments.
Off-chain you can use NEAR Indexer or JSONRPC EXPERIMENTAL_tx_status method to get all the receipts and their outcomes