From ca947d89ada67b09c3590b754ea7a83e624e93d2 Mon Sep 17 00:00:00 2001 From: SylTi Date: Thu, 17 Aug 2017 19:55:43 +0200 Subject: [PATCH 1/2] WIP EternalStorage --- contracts/data/EternalStorage.sol | 143 ++++++++++++++++++ .../SampleContractWithEternalStorage.sol | 27 ++++ .../examples/SampleLibraryEternalStorage.sol | 19 +++ test/EternalStorage.js | 131 ++++++++++++++++ test/SampleContractWithEternalStorage.js | 34 +++++ 5 files changed, 354 insertions(+) create mode 100644 contracts/data/EternalStorage.sol create mode 100644 contracts/examples/SampleContractWithEternalStorage.sol create mode 100644 contracts/examples/SampleLibraryEternalStorage.sol create mode 100644 test/EternalStorage.js create mode 100644 test/SampleContractWithEternalStorage.js 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..1baf2cc8547 --- /dev/null +++ b/contracts/examples/SampleContractWithEternalStorage.sol @@ -0,0 +1,27 @@ +pragma solidity ^0.4.13; + +import "../lifecycle/Pausable.sol"; +import "../examples/SampleLibraryEternalStorage.sol"; + +contract SampleContractWithEternalStorage is Pausable { + + using CounterLibrary for address; + address eternalStorage; + + function SampleContractWithEternalStorage(address _eternalStorage) { + eternalStorage = _eternalStorage; + } + + + function incrementValue() public returns (uint) { + return eternalStorage.increment(); + } + + function getValue() public returns (uint) { + return eternalStorage.getCount(); + } + +} + // function transferEternalStorageOwnership(address recipient) public onlyOwner { + // Ownable(eternalStorage).transferOwnership(recipient); + // } diff --git a/contracts/examples/SampleLibraryEternalStorage.sol b/contracts/examples/SampleLibraryEternalStorage.sol new file mode 100644 index 00000000000..b4872076317 --- /dev/null +++ b/contracts/examples/SampleLibraryEternalStorage.sol @@ -0,0 +1,19 @@ +pragma solidity ^0.4.13; + +import '../math/SafeMath.sol'; +import "../data/EternalStorage.sol"; + +library CounterLibrary { + using SafeMath for uint256; + + function getCount(address _storageContract) public constant returns(uint256) { + return EternalStorage(_storageContract).UIntValues(sha3("counter")); + } + + function increment(address _storageContract) public returns (uint) { + uint count = getCount(_storageContract); + uint value = count.add(1); + EternalStorage(_storageContract).setUIntValue(sha3("counter"), value); + return value; + } +} \ No newline at end of file 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..dad862cf8d7 --- /dev/null +++ b/test/SampleContractWithEternalStorage.js @@ -0,0 +1,34 @@ +'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, 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, 1); + }); + +}); From 4b38cae66b80109161c98a214b7f1cb25971e225 Mon Sep 17 00:00:00 2001 From: SylTi Date: Fri, 18 Aug 2017 18:17:50 +0200 Subject: [PATCH 2/2] fixes test & deploy lib & commentary --- .../SampleContractWithEternalStorage.sol | 17 ++++++++++++----- .../examples/SampleLibraryEternalStorage.sol | 15 ++++++++++++++- migrations/2_deploy_contracts.js | 5 +++++ test/SampleContractWithEternalStorage.js | 5 ++--- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/contracts/examples/SampleContractWithEternalStorage.sol b/contracts/examples/SampleContractWithEternalStorage.sol index 1baf2cc8547..400a844ef66 100644 --- a/contracts/examples/SampleContractWithEternalStorage.sol +++ b/contracts/examples/SampleContractWithEternalStorage.sol @@ -3,6 +3,11 @@ 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; @@ -12,16 +17,18 @@ contract SampleContractWithEternalStorage is Pausable { eternalStorage = _eternalStorage; } - + /** + * @dev call library function to increment the value in EternalStorage + */ function incrementValue() public returns (uint) { return eternalStorage.increment(); } - function getValue() public returns (uint) { + /** + * @dev call library function to get value stored in EternalStorage + */ + function getValue() public constant returns (uint) { return eternalStorage.getCount(); } } - // function transferEternalStorageOwnership(address recipient) public onlyOwner { - // Ownable(eternalStorage).transferOwnership(recipient); - // } diff --git a/contracts/examples/SampleLibraryEternalStorage.sol b/contracts/examples/SampleLibraryEternalStorage.sol index b4872076317..8f3b7a265bb 100644 --- a/contracts/examples/SampleLibraryEternalStorage.sol +++ b/contracts/examples/SampleLibraryEternalStorage.sol @@ -3,17 +3,30 @@ 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; } -} \ No newline at end of file +} 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/SampleContractWithEternalStorage.js b/test/SampleContractWithEternalStorage.js index dad862cf8d7..5dcc8eb6817 100644 --- a/test/SampleContractWithEternalStorage.js +++ b/test/SampleContractWithEternalStorage.js @@ -16,7 +16,7 @@ contract('SampleContractWithEternalStorage', function (accounts) { it('should start with a count of 0', async function () { let value = await sample.getValue(); - assert.equal(value, 0); + assert.equal(value.toNumber(), 0); }); it('should fail to write to eternalStorage', async function () { @@ -27,8 +27,7 @@ contract('SampleContractWithEternalStorage', function (accounts) { await storage.transferOwnership(sample.address); await sample.incrementValue(); let value = await sample.getValue(); - - assert.equal(value, 1); + assert.equal(value.toNumber(), 1); }); });