In the previous chapter, you built an end-to-end Dapp from scratch for the first time, using most of the knowledge you’d acquired throughout the book and most of the available Ethereum development tools. Although it was a good exercise that helped you to consolidate your Ethereum development skills, you might not be ready to deploy your first Dapp into the public production network. Before doing so, you might want to consider strengthening your Dapp so it can cope with real-world operational requirements, such as being able to generate, store (on the blockchain), and query (from the blockchain) event logs; upgrade your libraries after deploying them; and upgrade your contracts after deploying them.
I’ve already touched on some of these topics briefly, but in this chapter, I’ll revisit them in more detail. I’ll keep my usual pragmatic approach, and I’ll walk you through examples and code. Designing an upgradeable library or contract, though, requires advanced techniques whose implementation touches Solidity syntax that’s outside the scope of this book. But I still believe you should be aware of the concepts, which I’ll try to explain by sketching some contract or sequence diagrams.
You might be surprised I haven’t included security in the list. I consider it such an important topic that I’m dedicating the entire next chapter to it.
Let’s start with event logging. You already know how to declare and raise events on smart contracts. In the previous chapter, you saw how to handle contract events from a web UI.
For example, you refreshed the workflow status description at the top of both admin and voter pages every time you moved to a new step of the voting process. Also, you refreshed the proposals table at the top of the voter page every time a voter registered a new proposal. The entire workflow of the proposal registration, with related event handling, is illustrated in figure 13.1, which, if you remember, is similar to figure 1.8 from chapter 1. If you didn’t fully get it in chapter 1, it should definitely click now.
If you’ve done any event-driven or reactive programming before, you might think there’s nothing unusual about how contracts publish events and how a client subscribed to them, such as a web page, can handle them. But contracts differ in an important way: whereas in most languages, events are transient objects that disappear from the system after being handled, Ethereum events are logged permanently on the blockchain. As a result, you can also retrieve events that happened before starting up the UI client, if you want. Curious to see how? Keep reading!
You’ve already seen how to register listeners of contract events on your JavaScript with eventFilter.watch. For example, you wrote this code at the top of SimpleVoting.js (though I’m slightly simplifying it here) for registering a callback to a workflow status change event:
var workflowStatusChangeEvent = contractInstance.WorkflowStatusChangeEvent(); 1 workflowStatusChangeEvent .watch(function(error, result) { 2 if (!error) refreshWorkflowStatus(); else console.log(error); });
The variable workflowStatusChangeEvent is known as an event filter because it defines the set of events to listen to—in our case, all events of type WorkflowStatusChangeEvent. As you’ll see later, you can define the filter in a more restrictive way.
When you register a callback to an event filter through eventFilter.watch, you’ll handle events that take place from the moment the web page is opened. If you want to access past events—for example, to verify that all the workflow status changes took place in the correct sequence—you can do so through eventFilter.get, which accepts a similar callback as watch. First of all, you must define the filter in a slightly different way, by specifying the starting block and final block of your event search:
var pastWorkflowStatusChangeEvent = contractInstance.WorkflowStatusChangeEvent( {}, {fromBlock: 5000, toBlock: 'latest'}); 1
Then you register the callback to the event filter with get rather than watch:
pastWorkflowStatusChangeEvent.get( function(error, logs) { 1 if (!error) logs.forEach(log => console.log(log.args)); 2 else console.log(error); });
If you want to retrieve all past events the contract has broadcast, not only the WorkflowStatusChangeEvent events, you can create the event filter using the allEvents function, as follows:
var allEventPastEvents = contractInstance.allEvents( {}, {fromBlock: 1000000, toBlock: 'latest'}); 1
Then you can register a callback on them through get, as before:
allEventPastEvents.get((error, logs) => { logs.forEach(log => console.log(log.args)) });
If you want to retrieve only a subset of past events—for example, only the proposals that a certain voter registered—you can perform the search more efficiently by indexing some of the parameters of the event in question. You’ll see how in a moment.
When declaring an event on a contract, you can index up to three of its parameters. Event filters associated with these parameters will perform much better. For example, in the case of SimpleCoin, you might remember from section 3.5.1 that the Transfer event was defined as
event Transfer(address indexed from, address indexed to, uint256 value);
This allows you to efficiently filter transfer events against specific source and destination addresses when retrieving event logs:
var specificSourceAccountAddress = '0x8062a8850ef59dCd3469A83E04b4A40692a873Ba'; var transactionsFromSpecificAccount = simpleCoinInstance.Transferred( {from: specificSourceAccountAddress}, {fromBlock: 5000, toBlock: 'latest'}); transactionsFromSpecificAccount.get((error, logs) => { logs.forEach(log => console.log(log.args)); });
In the case of SimpleVoting, you might remember that the ProposalRegisteredEvent event was defined as
event ProposalRegisteredEvent(uint proposalId);
As it stands, you can’t use this event in a meaningful way as a log. How would you modify its definition so that you could query it to provide useful information? Think about it for a few seconds.... You could amend it like this:
event ProposalRegisteredEvent( address author, uint proposalId, string proposalDescription);
As an exercise, here’s a question for you: How would you index this event to efficiently retrieve all the proposals that have been registered from a specific address? Take your time... Yes, you’re right! Obviously, you’d index the author parameter as
event ProposalRegisteredEvent( address indexed author, uint proposalId, string proposalDescription);
Let’s now move to a completely different topic, also important from an operational point of view: What do you do when something goes wrong in your contract? Is it possible to fix it and redeploy it as you would for a conventional software component, such as a library or a microservice? I’ll discuss the answer to that question in the next few sections.
When developing a new contract, some of your requirements might be similar to those of other contracts that other developers have already deployed on the blockchain. You saw in chapter 7 how you can place common functionality into libraries and reference it from various contracts, therefore avoiding wasted reimplementation effort and code duplication. For example, you practiced how to perform safe arithmetic operations with a copy of the OpenZeppelin SafeMath library. What you did, though, is still not ideal: your deployment of OpenZeppelin libraries may be only one of the many instances present on the blockchain. Other developers, like you, may have included a copy of such libraries in their solution and may have performed their own private deployment, as shown in figure 13.2.
You can imagine how many instances of OpenZeppelin libraries are out there on TESTNET and MAINNET! The drawbacks of this approach include the following:
The most obvious solution to these issues is to have one official golden instance of the library on the blockchain and expect all its clients to reference it at that single address, as you saw briefly in chapter 7, section 7.5.2. This solution has one major drawback: once you’ve deployed a library, you can’t patch it. If you find a problem, a new deployment is necessary, with the consequence that all its client contracts must also be repointed to the new library address and redeployed, as shown in figure 13.3. This is far from an ideal solution!
An elegant solution that Manuel Araoz, CTO of Zeppelin, presented in an article on Medium[1] is to create a proxy to the library. His solution is a library proxy contract that acts as a layer of indirection between a client contract using the library and the library itself, as illustrated in figure 13.4.
See “Proxy Libraries in Solidity,” March 6, 2017, http://mng.bz/Xgd1.
These are the components of the proxy library solution:
When a client contract invokes a library function, it performs a corresponding delegatecall on the library proxy instead. This retrieves the correct library address for the client contract to use from the library proxy registry and then performs a delegatecall on the relevant instance of the library. From the point of view of the client contract, the whole call chain behaves exactly like a normal library function invocation.
Library functions are implicitly invoked through the delegatecall EVM opcode, which executes in the context of the caller, as explained in section 3.2.5, and as I’ll further explain in chapter 14 on security.
In summary, if you find that a library you’d like to use provides a proxy, use it rather than hardwiring to a specific deployed instance. You’ll gain maintainability. The catch, though, is that you must trust the contract owner won’t maliciously forward your calls to a new implementation of the library that has nothing to do with the original one.
If you find the idea of designing an upgradeable library interesting and want to learn further, I encourage you to review Araoz’s Medium article, where you can find code samples.
You should thoroughly understand the proxy library technique before you attempt to implement it; weak implementations have been found subject to malicious attacks. I recommend reading the Medium post “Malicious backdoors in Ethereum Proxies,” by Patricio Palladino, to make sure you’re aware of the weak spots: http://mng.bz/vNz1.
As you might have already guessed, contracts suffer from the same limitation as libraries: once you deploy a contract, you’re stuck with it. If you discover a bug or security vulnerability after deployment, you can at most freeze it or destroy it—if you included this kind of panic button functionality. The best bet is to plan for the worst-case scenario beforehand and design your contract with built-in upgradeability.
As in the case of proxy libraries, although this solution makes the contract safer from your side, from the point of view of the users, you’re introducing a layer of obfuscation. The most security-conscious users might not trust your good intentions and might fear the possibility that, all of a sudden, you could start redirecting client calls to a malicious contract.
I’ll illustrate for you these two main techniques for making a contract upgradeable:
Imagine various contracts, among which SimpleCrowdsale, from chapters 6 and 7, would like to use your marvelous SimpleCoin contract. You decide to make SimpleCoin upgradeable through a SimpleCoinProxy contract to avoid headaches not only to you, but also to all of your clients. As you can see in figure 13.5, the solution is identical to what you saw for SafeMath.
Nick Johnson, the author of ENS, has taken a slightly different approach to making a contract upgradeable.[2] He has proposed to split the state (storage and Ether) and the
See “Mad blockchain science: A 100% upgradeable contract,” Reddit+r/ethereum, May 24, 2016, http://mng.bz/y1Yo.
logic of a contract into two separate contracts, both inherited from a common abstract contract called Upgradeable:
With this design, illustrated in figure 13.6, when the target contract got upgraded, the dispatcher would still keep an unaltered state while it transferred all the calls to the latest version of the target contract.
If you’re eager to try out this approach, I encourage you to look at Nick’s code at http://mng.bz/4OZD. If you’re interested in learning more about solutions around library or contract upgradeability, I recommend the Medium article “Summary of Ethereum Upgradeable Smart Contract R&D,” by Jack Tanner, based on his research across all the currently known techniques: http://mng.bz/QQmR.
Designing an upgradeable contract using Nick’s technique requires a thorough understanding of the inner workings of EVM and EVM assembly, which is outside the scope of this book. Also, using an upgradeable contract poses risks, as I explained earlier.