diff --git a/contracts/data/EternalStorage.sol b/contracts/data/EternalStorage.sol new file mode 100644 index 00000000000..37379cecd7e --- /dev/null +++ b/contracts/data/EternalStorage.sol @@ -0,0 +1,143 @@ +pragma solidity ^0.4.13; + +import "../lifecycle/Pausable.sol"; + +/** + * @title Contract that store data in behalf of another contract + * @author SylTi inspired from colony blog post https://blog.colony.io/writing-upgradeable-contracts-in-solidity-6743f0eecc88 + * @dev This contract should be used in combinaison of other strategies to make your contract upgradeable + * like encapsulating logic into libraries. + */ + +contract EternalStorage is Pausable { + mapping(bytes32 => uint) public UIntValues; + mapping(bytes32 => int) public IntValues; + mapping(bytes32 => string) public StringValues; + mapping(bytes32 => address) public AddressValues; + mapping(bytes32 => bytes) public BytesValues; + mapping(bytes32 => bytes32) public Bytes32Values; + mapping(bytes32 => bool) public BooleanValues; + + function EternalStorage() { + } + + /** + * @dev change the value inside UIntValues for the given key + * @param record sha3 key + * @param value new value is uint + */ + function setUIntValue(bytes32 record, uint value) public onlyOwner { + UIntValues[record] = value; + } + + /** + * @dev delete the value inside UIntValues for the given key + * @param record sha3 key + */ + function deleteUIntValue(bytes32 record) public onlyOwner { + delete UIntValues[record]; + } + + /** + * @dev change the value inside IntValues for the given key + * @param record sha3 key + * @param value new value is int + */ + function setIntValue(bytes32 record, int value) public onlyOwner { + IntValues[record] = value; + } + + /** + * @dev delete the value inside IntValues for the given key + * @param record sha3 key + */ + function deleteIntValue(bytes32 record) public onlyOwner { + delete IntValues[record]; + } + + /** + * @dev change the value inside StringValues for the given key + * @param record sha3 key + * @param value new value is string + */ + function setStringValue(bytes32 record, string value) public onlyOwner { + StringValues[record] = value; + } + + /** + * @dev delete the value inside StringValues for the given key + * @param record sha3 key + */ + function deleteStringValue(bytes32 record) public onlyOwner { + delete StringValues[record]; + } + + /** + * @dev change the value inside AddressValues for the given key + * @param record sha3 key + * @param value new value is address + */ + function setAddressValue(bytes32 record, address value) public onlyOwner { + AddressValues[record] = value; + } + + /** + * @dev delete the value inside AddressValues for the given key + * @param record sha3 key + */ + function deleteAddressValue(bytes32 record) public onlyOwner { + delete AddressValues[record]; + } + + /** + * @dev change the value inside BytesValues for the given key + * @param record sha3 key + * @param value new value is bytes + */ + function setBytesValue(bytes32 record, bytes value) public onlyOwner { + BytesValues[record] = value; + } + + /** + * @dev delete the value inside BytesValues for the given key + * @param record sha3 key + */ + function deleteBytesValue(bytes32 record) public onlyOwner { + delete BytesValues[record]; + } + + /** + * @dev change the value inside Bytes32Values for the given key + * @param record sha3 key + * @param value new value is bytes32 + */ + function setBytes32Value(bytes32 record, bytes32 value) public onlyOwner { + Bytes32Values[record] = value; + } + + /** + * @dev delete the value inside Bytes32Values for the given key + * @param record sha3 key + */ + function deleteBytes32Value(bytes32 record) public onlyOwner { + delete Bytes32Values[record]; + } + + /** + * @dev change the value inside BooleanValues for the given key + * @param record sha3 key + * @param value new value is bool + */ + function setBooleanValue(bytes32 record, bool value) public onlyOwner { + BooleanValues[record] = value; + } + + /** + * @dev delete the value inside BooleanValues for the given key + * @param record sha3 key + */ + function deleteBooleanValue(bytes32 record) public onlyOwner { + delete BooleanValues[record]; + } + +} \ No newline at end of file diff --git a/contracts/examples/SampleContractWithEternalStorage.sol b/contracts/examples/SampleContractWithEternalStorage.sol new file mode 100644 index 00000000000..400a844ef66 --- /dev/null +++ b/contracts/examples/SampleContractWithEternalStorage.sol @@ -0,0 +1,34 @@ +pragma solidity ^0.4.13; + +import "../lifecycle/Pausable.sol"; +import "../examples/SampleLibraryEternalStorage.sol"; + +/** + * @title Sample contract that show how to use the EternalStorage contract + * @author SylTi inspired from colony blog post https://blog.colony.io/writing-upgradeable-contracts-in-solidity-6743f0eecc88 + * @dev This contract is just an example and should not be used directly + */ +contract SampleContractWithEternalStorage is Pausable { + + using CounterLibrary for address; + address eternalStorage; + + function SampleContractWithEternalStorage(address _eternalStorage) { + eternalStorage = _eternalStorage; + } + + /** + * @dev call library function to increment the value in EternalStorage + */ + function incrementValue() public returns (uint) { + return eternalStorage.increment(); + } + + /** + * @dev call library function to get value stored in EternalStorage + */ + function getValue() public constant returns (uint) { + return eternalStorage.getCount(); + } + +} diff --git a/contracts/examples/SampleLibraryEternalStorage.sol b/contracts/examples/SampleLibraryEternalStorage.sol new file mode 100644 index 00000000000..8f3b7a265bb --- /dev/null +++ b/contracts/examples/SampleLibraryEternalStorage.sol @@ -0,0 +1,32 @@ +pragma solidity ^0.4.13; + +import '../math/SafeMath.sol'; +import "../data/EternalStorage.sol"; + +/** + * @title Sample library that show how to store/read data in the EternalStorage contract + * @author SylTi inspired from colony blog post https://blog.colony.io/writing-upgradeable-contracts-in-solidity-6743f0eecc88 + * @dev This is just a example lib and should not be used directly + */ +library CounterLibrary { + using SafeMath for uint256; + + /** + * @dev read value from EternalStorage contract + * @param _storageContract address of the EternalStorage contract + */ + function getCount(address _storageContract) public constant returns(uint256) { + return EternalStorage(_storageContract).UIntValues(sha3("counter")); + } + + /** + * @dev increment value in EternalStorage by one + * @param _storageContract address of the EternalStorage contract + */ + function increment(address _storageContract) public returns (uint) { + uint count = getCount(_storageContract); + uint value = count.add(1); + EternalStorage(_storageContract).setUIntValue(sha3("counter"), value); + return value; + } +} diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index 2ce0dfa08e7..2b0eb75eab9 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -1,5 +1,10 @@ //var Ownable = artifacts.require("ownership/Ownable.sol"); +let SampleLibraryEternalStorage = artifacts.require("CounterLibrary"); +let SampleContractWithEternalStorage = artifacts.require("SampleContractWithEternalStorage"); module.exports = function(deployer) { //deployer.deploy(Ownable); + deployer.deploy(SampleLibraryEternalStorage); + deployer.link(SampleLibraryEternalStorage, SampleContractWithEternalStorage); + deployer.deploy(SampleContractWithEternalStorage); }; diff --git a/test/EternalStorage.js b/test/EternalStorage.js new file mode 100644 index 00000000000..1d69ab69370 --- /dev/null +++ b/test/EternalStorage.js @@ -0,0 +1,131 @@ +'use strict'; + +import expectThrow from './helpers/expectThrow'; +let EternalStorage = artifacts.require('../contracts/data/EternalStorage.sol'); + +contract('EternalStorage', function (accounts) { + let storage; + + beforeEach(async function () { + storage = await EternalStorage.new(); + }); + + contract('UIntValues', function () { + it('should start with a value of 0', async function () { + let value = await storage.UIntValues(web3.sha3("value")); + + assert.equal(value, 0); + }); + + it('should set value to 1', async function () { + await storage.setUIntValue(web3.sha3("value"), 1); + let value = await storage.UIntValues(web3.sha3("value")); + + assert.equal(value, 1); + }); + it('should fail to set negative number', async function () { + await storage.setUIntValue(web3.sha3("value"), -1); + let value = await storage.UIntValues(web3.sha3("value")); + + assert.notEqual(value.toNumber(), -1); + }) + }); + + contract('IntValues', function () { + it('should start with a value of 0', async function () { + let value = await storage.IntValues(web3.sha3("value")); + + assert.equal(value, 0); + }); + + it('should set value to -1', async function () { + await storage.setIntValue(web3.sha3("value"), -1); + let value = await storage.IntValues(web3.sha3("value")); + + assert.equal(value, -1); + }); + + it('should fail to set an address', async function () { + await storage.setIntValue(web3.sha3("value"), accounts[0]) + let value = await storage.UIntValues(web3.sha3("value")); + assert.notEqual(value.valueOf(), accounts[0]); + }) + }); + + contract('StringValues', function () { + it('should start with a empty string', async function () { + let value = await storage.StringValues(web3.sha3("value")); + + assert.equal(value.valueOf(), ""); + }); + + it('should set string value to test', async function () { + await storage.setStringValue(web3.sha3("value"), "test"); + let value = await storage.StringValues(web3.sha3("value")); + + assert.equal(value.valueOf(), "test"); + }); + }); + + contract('AddressValues', function () { + it('should start with a empty Address', async function () { + let value = await storage.AddressValues(web3.sha3("value")); + + assert.equal(value.valueOf(), 0x0); + }); + + it('should set Address value to accounts[0]', async function () { + await storage.setAddressValue(web3.sha3("value"), accounts[0]); + let value = await storage.AddressValues(web3.sha3("value")); + + assert.equal(value.valueOf(), accounts[0]); + }); + }); + + contract('BytesValues', function () { + it('should start with a empty Bytes', async function () { + let value = await storage.BytesValues(web3.sha3("value")); + + assert.equal(value.valueOf(), "0x"); + }); + + it('should set Bytes value to substring of sha3(test)', async function () { + let insert = web3.sha3("test").substring(0, 10); + await storage.setBytesValue(web3.sha3("value"), insert); + let value = await storage.BytesValues(web3.sha3("value")); + + assert.equal(value.valueOf(), insert); + }); + }); + + contract('Bytes32Values', function () { + it('should start with a empty Bytes32', async function () { + let value = await storage.Bytes32Values(web3.sha3("value")); + + assert.equal(value.valueOf(), "0x0000000000000000000000000000000000000000000000000000000000000000"); + }); + + it('should set Bytes32 value to sha3(test)', async function () { + let insert = web3.sha3("test"); + await storage.setBytes32Value(web3.sha3("value"), insert); + let value = await storage.Bytes32Values(web3.sha3("value")); + + assert.equal(value.valueOf(), insert); + }); + }); + + contract('BooleanValues', function () { + it('should start with a empty Boolean', async function () { + let value = await storage.BooleanValues(web3.sha3("value")); + + assert.equal(value, false); + }); + + it('should set Boolean value to true', async function () { + await storage.setBooleanValue(web3.sha3("value"), true); + let value = await storage.BooleanValues(web3.sha3("value")); + + assert.equal(value, true); + }); + }); +}); diff --git a/test/SampleContractWithEternalStorage.js b/test/SampleContractWithEternalStorage.js new file mode 100644 index 00000000000..5dcc8eb6817 --- /dev/null +++ b/test/SampleContractWithEternalStorage.js @@ -0,0 +1,33 @@ +'use strict'; + +import expectThrow from './helpers/expectThrow'; +let EternalStorage = artifacts.require('../contracts/data/EternalStorage.sol'); +let SampleContract = artifacts.require('../contracts/examples/SampleContractWithEternalStorage.sol'); + +contract('SampleContractWithEternalStorage', function (accounts) { + let sample; + let storage; + + beforeEach(async function () { + storage = await EternalStorage.new(); + sample = await SampleContract.new(storage.address); + }); + + it('should start with a count of 0', async function () { + let value = await sample.getValue(); + + assert.equal(value.toNumber(), 0); + }); + + it('should fail to write to eternalStorage', async function () { + await expectThrow(sample.incrementValue()); + }) + + it('should set value to 1', async function () { + await storage.transferOwnership(sample.address); + await sample.incrementValue(); + let value = await sample.getValue(); + assert.equal(value.toNumber(), 1); + }); + +});