As you must be aware that smart contracts are immutable programs that run exactly as programmed without any third party interface. Smart contracts cannot be modified once they are deployed which is actually a feature of smart contracts but in some blockchain use-cases it may be needed to change the smart contracts interfaces and/or their implementation. In this tutorial we will learn the basic architecture you should adopt to make the interfaces and their implementations upgradable after smart contracts are deployed.
Our architecture will involve 4 contracts in total:
- Key-Value Store Contract: This contract will contain the data of your dapp in form of key-value store.
- Application Logic Contract: This contract will contain the application logic. The function in this contracts will add/update keys in the key-value store contract. To add/remove features in the application you simply need to destroy this contract and deploy a new contract pointing to the same key-value store therefore you preserve the state of the contract
- Events Contract: To make sure that we don’t loose the events when we upgrade the application logic contract we need to create a events contract to fire events. Make sure that the events are very generic i.e., different events are differentiated using the event arguments not the event names or parameter types. For example:
to fire event for renewable of tenancy contract you shouldn’t have a event name as “tenancyContractRenewed” instead a generic event with first argument as “tenancyContractRenewed” and the second argument as the tenancy contract id and so on. To make the events contract scaleable you can index events so that you don’t have to loop through all events to find event types. - Address Contract: Finally a contract to which your client will point to. This contract will contain the address of the application logic contract. So whenever you upgrade the application logic contract you can simply change the address of the application contract in the address contract and client will automatically point to the new contract.
Here is an example of key-value store and application logic contract:
contract DataStore
{
address owner;
address appContract;
mapping (string => string) kayValueStore;
function str_to_bytes(string str) constant returns (byte[250]){
bytes memory b = bytes(str);
byte[250] memory final_str;
for(uint i; i<b.length; i++){
final_str[i] = b[i];
}
return final_str;
}
function DataStore()
{
owner = msg.sender;
}
function changeAppContract(address _appContract)
{
if(msg.sender == owner)
{
appContract = _appContract;
}
}
function updateKey(string key, string value)
{
if(msg.sender == appContract)
{
kayValueStore[key] = value;
}
}
function getValue(string key) returns (byte[250] value)
{
return str_to_bytes(kayValueStore[key]);
}
}
contract MainContract
{
DataStore store;
function MainContract(address _DataStore)
{
store = DataStore(_DataStore);
}
function updateData()
{
store.updateKey("Name", "Dave");
}
function getData() returns (string name)
{
bytes memory bytesStringTrimmed = new bytes(250);
for(uint iii = 0; iii < 250; iii++)
{
bytesStringTrimmed[iii] = store.getValue("Name")[iii];
}
return string(bytesStringTrimmed);
}
}
Here we are storing the key and value both as strings in the DataStore
contract. As a contract cannot read a string returned by another contract due to EVM’s limitation therefore we are converting the string to bytes[250]
(assuming that the value length will be less than equal to 250 always) and then again converting it back to string
once read. If your strings are likely to be above 250 characters then you can change the length in the source code.
Here we have also set permissioning stating that only the MainContract
i.e., the application logic contract can only write to the key-value store.
Here is the web3js code to test the above contracts:
var browser_untitled_datastoreContract = web3.eth.contract([{"constant":true,"inputs":[{"name":"str","type":"string"}],"name":"str_to_bytes","outputs":[{"name":"","type":"bytes1[250]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"key","type":"string"}],"name":"getValue","outputs":[{"name":"value","type":"bytes1[250]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_appContract","type":"address"}],"name":"changeAppContract","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"key","type":"string"},{"name":"value","type":"string"}],"name":"updateKey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]);
var browser_untitled_datastore = browser_untitled_datastoreContract.new(
{
from: web3.eth.accounts[0],
data: '0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506107278061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630fd9c61b14610067578063960384a014610100578063eacbb41c14610199578063fbececac146101d2575b600080fd5b341561007257600080fd5b6100c2600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610272565b604051808260fa60200280838360005b838110156100ed5780820151818401526020810190506100d2565b5050505090500191505060405180910390f35b341561010b57600080fd5b61015b600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610367565b604051808260fa60200280838360005b8381101561018657808201518184015260208101905061016b565b5050505090500191505060405180910390f35b34156101a457600080fd5b6101d0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610482565b005b34156101dd57600080fd5b610270600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061051c565b005b61027a6105f7565b610282610642565b61028a6105f7565b60008492505b825181101561035c5782818151811015156102a757fe5b9060200101517f010000000000000000000000000000000000000000000000000000000000000090047f010000000000000000000000000000000000000000000000000000000000000002828260fa8110151561030057fe5b60200201907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815250508080600101915050610290565b819350505050919050565b61036f6105f7565b61047b6002836040518082805190602001908083835b6020831015156103aa5780518252602082019150602081019050602083039250610385565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156104715780601f1061044657610100808354040283529160200191610471565b820191906000526020600020905b81548152906001019060200180831161045457829003601f168201915b5050505050610272565b9050919050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156105195780600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b50565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156105f357806002836040518082805190602001908083835b6020831015156105ab5780518252602082019150602081019050602083039250610586565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902090805190602001906105f1929190610656565b505b5050565b611f406040519081016040528060fa905b60007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001906001900390816106085790505090565b602060405190810160405280600081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061069757805160ff19168380011785556106c5565b828001600101855582156106c5579182015b828111156106c45782518255916020019190600101906106a9565b5b5090506106d291906106d6565b5090565b6106f891905b808211156106f45760008160009055506001016106dc565b5090565b905600a165627a7a723058201b24b6f574ff8c94c714d43bf887a017b3808ae99d1e3669ead0a392ee91ebe10029',
gas: '4700000', gasPrice: 0
}, function (e, contract){
console.log(e, contract);
if (typeof contract.address !== 'undefined') {
DatastoreInstance = browser_untitled_datastoreContract.at(contract.address);
var _DataStore = contract.address;
var browser_untitled_maincontractContract = web3.eth.contract([{"constant":false,"inputs":[],"name":"getData","outputs":[{"name":"name","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"updateData","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_DataStore","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]);
var browser_untitled_maincontract = browser_untitled_maincontractContract.new(
_DataStore,
{
from: web3.eth.accounts[0],
data: '0x6060604052341561000f57600080fd5b60405160208061046783398101604052808051906020019091905050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506103ec8061007b6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bc5de30146100515780638e00a2bb146100df575b600080fd5b341561005c57600080fd5b6100646100f4565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100a4578082015181840152602081019050610089565b50505050905090810190601f1680156100d15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156100ea57600080fd5b6100f2610289565b005b6100fc610398565b6101046103ac565b600060fa6040518059106101155750595b9080825280601f01601f19166020018201604052509150600090505b60fa811015610281576000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663960384a06000604051611f4001526040518163ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018080602001828103825260048152602001807f4e616d6500000000000000000000000000000000000000000000000000000000815250602001915050611f4060405180830381600087803b151561020557600080fd5b6102c65a03f1151561021657600080fd5b50505060405180611f40016040528160fa8110151561023157fe5b6020020151828281518110151561024457fe5b9060200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508080600101915050610131565b819250505090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663fbececac6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808060200180602001838103835260048152602001807f4e616d6500000000000000000000000000000000000000000000000000000000815250602001838103825260048152602001807f446176650000000000000000000000000000000000000000000000000000000081525060200192505050600060405180830381600087803b151561038257600080fd5b6102c65a03f1151561039357600080fd5b505050565b602060405190810160405280600081525090565b6020604051908101604052806000815250905600a165627a7a72305820de9f93d7b31329849e20fcad8bb94412ea3ccb09b6032e5564a8c04be72269140029',
gas: '4700000', gasPrice: 0
}, function (e, contract){
console.log(e, contract);
if (typeof contract.address !== 'undefined') {
MainContractInstance = browser_untitled_maincontractContract.at(contract.address);
DatastoreInstance.changeAppContract.sendTransaction(contract.address, {
from: web3.eth.accounts[0],
gas: '4700000'
}, function(){
MainContractInstance.updateData.sendTransaction({
from: web3.eth.accounts[0],
gas: '4700000'
}, function(e, r){
console.log(e, r);
setTimeout(function(){
MainContractInstance.getData.call({from: web3.eth.accounts[0], gas: '4700000', gasPrice: 0}, function(e, r){
console.log(e, r);
DatastoreInstance.getValue.call("Name", {from: web3.eth.accounts[0], gas: '4700000', gasPrice: 0}, function(e, r){
console.log(e, r);
});
});
}, 100)
})
})
}
})
}
})