Smart Contract : Maintenance Patterns

Seungwon Go
Dec 20, 2018 · 5 min read

By Seungwon Go, CEO & Founder at ReturnValues (seungwon.go@returnvalues.com)

Data Segregation Pattern

The data segregation pattern separates contract logic from its underlying data. So you develop business logic part and data storage part in separate smart contracts.

The example below shows how to separate business logic and data storage into two smart contracts.

contract DataStorage { 
mapping(bytes32 => uint) uintStorage;
function getUintValue(bytes32 key) public view returns (uint) {
return uintStorage[key];
}
function setUintValue(bytes32 key, uint value) public {
uintStorage[key] = value;
}
}

The contract given above has two functions in it that stores data with key-value and loads data with key-value.

import "./DataStorage.sol";contract BusinessLogic { 
DataStorage dataStorage;
function setDataStorage(address _address) public {
dataStorage = DataStorage(_address);
}
function setData(string _key, uint _value) public {
bytes32 key = keccak256(_key);
dataStorage.setUintValue(key, _value);
}
function getData(string _key) public returns (uint) {
bytes32 key = keccak256(_key);
return dataStorage.getUintValue(key);
}
}

The DataStorage contract is already deployed on blockchain, and the contract given above uses the address of the DataStorage contract to set or get data of the contract, by setting the address using setDataStorage.

In this way, you can implement data storage part and business logic part and when you need to upgrade the contract for the business logic part, you can still use the contract for the data storage as deployed on blockchain.

The contracts below are the examples for storing data according to their data types.

contract DataStorage {     
mapping(bytes32 => uint) UIntStorage;
mapping(bytes32 => string) StringStorage;
mapping(bytes32 => address) AddressStorage;
mapping(bytes32 => bytes) BytesStorage;
mapping(bytes32 => bool) BooleanStorage;
mapping(bytes32 => int) IntStorage;
function setUIntValue(bytes32 record, uint value) {
UIntStorage[record] = value;
}
function getUIntValue(bytes32 record) constant returns (uint){
return UIntStorage[record];
}
function setStringValue(bytes32 record, string value) {
StringStorage[record] = value;
}
function getStringValue(bytes32 record) constant returns (string){
return StringStorage[record];
}
function setAddressValue(bytes32 record, address value) {
AddressStorage[record] = value;
}
function getAddressValue(bytes32 record) constant returns (address){
return AddressStorage[record];
}
function setBytesValue(bytes32 record, bytes value) {
BytesStorage[record] = value;
}
function getBytesValue(bytes32 record) constant returns (bytes){
return BytesStorage[record];
}
function setBooleanValue(bytes32 record, bool value) {
BooleanStorage[record] = value;
}
function getBooleanValue(bytes32 record) constant returns (bool){
return BooleanStorage[record];
}
function setIntValue(bytes32 record, int value) {
IntStorage[record] = value;
}
function getIntValue(bytes32 record) constant returns (int){
return IntStorage[record];
}
}

Satellite Pattern

In this pattern, you choose critical functions that are complex and apt to change or improvement and implement them in separate smart contract.

One of my latest projects was to develop ERP system for a customer. At the time when we first began our project, the invoice was supposed to be issued at the date of shipment according to the accounting standards. After we launched the project, however, the IRS standard the international accounting standards have changed under which the invoice was now supposed to be issued at the delivery day. So we needed to change many parts in the project and it took a lot of efforts. If we had developed this project as a smart contract l on blockchain and had not considered or predicted such change, we wouldn’t have been able to find a solution to the problem.

So in the case of a business logic that can be changed in the future or a function that deals with a critical business logic, you can implement them in a separate contract so that you can take action in case you find a bug or need to improve them.

In the contract below, I implemented a function that deals with a certain calculation in a separate contract.

contract Satellite { 
function calculateVariable(uint _a, uint _b) public pure returns (uint){
// calculate var
return _a * _b;
}
}

In the contract below, we implemented so that we can use the address of the contract above that is already deployed on blockchain in the SatelliteExample contract by setting the address using setSatelliteAddress function.

import "../../authorization/Ownership.sol"; 
import "./Satellite.sol";
contract SatelliteExample is Owned {
uint public variable;
address satelliteAddress;
function setVariable(uint _a, uint _b) public onlyOwner {
Satellite s = Satellite(satelliteAddress);
variable = s.calculateVariable(_a, _b);
}
function setSatelliteAddress(address _address) public onlyOwner {
satelliteAddress = _address;
}
}

In this way, you can implement a critical function in a separate contract and deploy in to blockchain and when you need to change the function, you can create and deploy another contract that includes the updated function and using updateSatelikeAddress function, you can set the address to point to the new contract.

Contract Register Pattern

This pattern is used to manage the versions of a contract in case you develop new versions of a contract.

For example, in the contract below, let’s assume that data is stored in contractAddress. For a certain reason, let’s say the structure of data has to change and you need to make a new contract with a new structure of data and deploy it. In this case, you change the newly deployed contractAddress by using changeContract function and you save the address in the variable contractVersions. Then you can refer to the data that has the structure of the version under which the contract was developed.

import "../authorization/Ownership.sol";contract Register is Owned { 
address contractAddress;
address[] contractVersions;
function changeContract(address _newContract) public onlyOwner() returns (bool) {
if(_newContract != dataContract) {
contractVersions.push(_newContract);
contractAddress = _newContract;
return true;
}
return false;
}
}

Contract Relay Pattern

With this development pattern, you can manage new versions of a contract. However, this pattern has a limit that you cannot use this pattern unless the structure of the new contract is the same as that of the ol done.

import "../authorization/Ownership.sol";contract Relay is Owned { 
address public currentVersion;
constructor(address _address) public {
currentVersion = _address;
owner = msg.sender;
}
function changeContract(address _newVersion) public onlyOwner() {
currentVersion = _newVersion;
}
// fallback function
function() public {
require(currentVersion.delegatecall(msg.data));
}
}

Other than these patterns above, there are many maintenance patterns. There’s a saying “Make sure that you’ve extinguished fire completely.” You need to be very thorough about smart contract development and it is never enough to check if your smart contract is properly developed. And you need to be prepared to deal with a maintenance issue.

ReturnValues

Seungwon Go

Written by

Founder & CEO at ReturnValues

ReturnValues

ReturnValues Blogs

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade