forked from cerc-io/laconicd-deprecated
tests: add solidity test suites (#487)
* tests: add solidity test suite * tests: remove require strings * Update tests-solidity/init-test-node.sh * Update tests-solidity/init-test-node.sh Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
co-authored by
Federico Kunze
parent
4344dc10c7
commit
c9639c3860
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
import "./TimeHelpers.sol";
|
||||
import "./UnstructuredStorage.sol";
|
||||
|
||||
|
||||
contract Initializable is TimeHelpers {
|
||||
using UnstructuredStorage for bytes32;
|
||||
|
||||
// keccak256("aragonOS.initializable.initializationBlock")
|
||||
bytes32 internal constant INITIALIZATION_BLOCK_POSITION = 0xebb05b386a8d34882b8711d156f463690983dc47815980fb82aeeff1aa43579e;
|
||||
|
||||
string private constant ERROR_ALREADY_INITIALIZED = "INIT_ALREADY_INITIALIZED";
|
||||
string private constant ERROR_NOT_INITIALIZED = "INIT_NOT_INITIALIZED";
|
||||
|
||||
modifier onlyInit {
|
||||
require(getInitializationBlock() == 0, ERROR_ALREADY_INITIALIZED);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier isInitialized {
|
||||
require(hasInitialized(), ERROR_NOT_INITIALIZED);
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Block number in which the contract was initialized
|
||||
*/
|
||||
function getInitializationBlock() public view returns (uint256) {
|
||||
return INITIALIZATION_BLOCK_POSITION.getStorageUint256();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the contract has been initialized by the time of the current block
|
||||
*/
|
||||
function hasInitialized() public view returns (bool) {
|
||||
uint256 initializationBlock = getInitializationBlock();
|
||||
return initializationBlock != 0 && getBlockNumber() >= initializationBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Function to be called by top level contract after initialization has finished.
|
||||
*/
|
||||
function initialized() internal onlyInit {
|
||||
INITIALIZATION_BLOCK_POSITION.setStorageUint256(getBlockNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Function to be called by top level contract after initialization to enable the contract
|
||||
* at a future block number rather than immediately.
|
||||
*/
|
||||
function initializedAt(uint256 _blockNumber) internal onlyInit {
|
||||
INITIALIZATION_BLOCK_POSITION.setStorageUint256(_blockNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
import "./Initializable.sol";
|
||||
|
||||
|
||||
contract Petrifiable is Initializable {
|
||||
// Use block UINT256_MAX (which should be never) as the initializable date
|
||||
uint256 internal constant PETRIFIED_BLOCK = uint256(-1);
|
||||
|
||||
function isPetrified() public view returns (bool) {
|
||||
return getInitializationBlock() == PETRIFIED_BLOCK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Function to be called by top level contract to prevent being initialized.
|
||||
* Useful for freezing base contracts when they're used behind proxies.
|
||||
*/
|
||||
function petrify() internal onlyInit {
|
||||
initializedAt(PETRIFIED_BLOCK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
import "./Uint256Helpers.sol";
|
||||
|
||||
|
||||
contract TimeHelpers {
|
||||
using Uint256Helpers for uint256;
|
||||
|
||||
/**
|
||||
* @dev Returns the current block number.
|
||||
* Using a function rather than `block.number` allows us to easily mock the block number in
|
||||
* tests.
|
||||
*/
|
||||
function getBlockNumber() internal view returns (uint256) {
|
||||
return block.number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the current block number, converted to uint64.
|
||||
* Using a function rather than `block.number` allows us to easily mock the block number in
|
||||
* tests.
|
||||
*/
|
||||
function getBlockNumber64() internal view returns (uint64) {
|
||||
return getBlockNumber().toUint64();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the current timestamp.
|
||||
* Using a function rather than `block.timestamp` allows us to easily mock it in
|
||||
* tests.
|
||||
*/
|
||||
function getTimestamp() internal view returns (uint256) {
|
||||
return block.timestamp; // solium-disable-line security/no-block-members
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the current timestamp, converted to uint64.
|
||||
* Using a function rather than `block.timestamp` allows us to easily mock it in
|
||||
* tests.
|
||||
*/
|
||||
function getTimestamp64() internal view returns (uint64) {
|
||||
return getTimestamp().toUint64();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
|
||||
library Uint256Helpers {
|
||||
uint256 private constant MAX_UINT64 = uint64(-1);
|
||||
|
||||
string private constant ERROR_NUMBER_TOO_BIG = "UINT64_NUMBER_TOO_BIG";
|
||||
|
||||
function toUint64(uint256 a) internal pure returns (uint64) {
|
||||
require(a <= MAX_UINT64, ERROR_NUMBER_TOO_BIG);
|
||||
return uint64(a);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
|
||||
library UnstructuredStorage {
|
||||
function getStorageBool(bytes32 position) internal view returns (bool data) {
|
||||
assembly { data := sload(position) }
|
||||
}
|
||||
|
||||
function getStorageAddress(bytes32 position) internal view returns (address data) {
|
||||
assembly { data := sload(position) }
|
||||
}
|
||||
|
||||
function getStorageBytes32(bytes32 position) internal view returns (bytes32 data) {
|
||||
assembly { data := sload(position) }
|
||||
}
|
||||
|
||||
function getStorageUint256(bytes32 position) internal view returns (uint256 data) {
|
||||
assembly { data := sload(position) }
|
||||
}
|
||||
|
||||
function setStorageBool(bytes32 position, bool data) internal {
|
||||
assembly { sstore(position, data) }
|
||||
}
|
||||
|
||||
function setStorageAddress(bytes32 position, address data) internal {
|
||||
assembly { sstore(position, data) }
|
||||
}
|
||||
|
||||
function setStorageBytes32(bytes32 position, bytes32 data) internal {
|
||||
assembly { sstore(position, data) }
|
||||
}
|
||||
|
||||
function setStorageUint256(bytes32 position, uint256 data) internal {
|
||||
assembly { sstore(position, data) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
pragma solidity 0.4.24;
|
||||
|
||||
import "../Initializable.sol";
|
||||
import "../Petrifiable.sol";
|
||||
|
||||
|
||||
contract LifecycleMock is Initializable, Petrifiable {
|
||||
function initializeMock() public {
|
||||
initialized();
|
||||
}
|
||||
|
||||
function petrifyMock() public {
|
||||
petrify();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.4.22 <0.8.0;
|
||||
|
||||
contract Migrations {
|
||||
address public owner = msg.sender;
|
||||
uint public last_completed_migration;
|
||||
|
||||
modifier restricted() {
|
||||
require(
|
||||
msg.sender == owner,
|
||||
"This function is restricted to the contract's owner"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
function setCompleted(uint completed) public restricted {
|
||||
last_completed_migration = completed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const Migrations = artifacts.require("Migrations");
|
||||
|
||||
module.exports = function (deployer) {
|
||||
deployer.deploy(Migrations);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "initializable",
|
||||
"version": "1.0.0",
|
||||
"author": "Aragon Association <contact@aragon.org>",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"test-ganache": "yarn truffle test",
|
||||
"test-ethermint": "yarn truffle test --network ethermint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aragon/contract-helpers-test": "^0.1.0",
|
||||
"chai": "^4.2.0",
|
||||
"truffle": "^5.1.42",
|
||||
"web3": "^1.2.11"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
const { assertRevert } = require('@aragon/contract-helpers-test/src/asserts')
|
||||
|
||||
// Mocks
|
||||
const LifecycleMock = artifacts.require('LifecycleMock')
|
||||
|
||||
const ERRORS = {
|
||||
INIT_ALREADY_INITIALIZED: 'INIT_ALREADY_INITIALIZED',
|
||||
}
|
||||
|
||||
contract('Lifecycle', () => {
|
||||
let lifecycle
|
||||
|
||||
beforeEach(async () => {
|
||||
lifecycle = await LifecycleMock.new()
|
||||
})
|
||||
|
||||
it('is not initialized', async () => {
|
||||
assert.isFalse(await lifecycle.hasInitialized(), 'should not be initialized')
|
||||
})
|
||||
|
||||
it('is not petrified', async () => {
|
||||
assert.isFalse(await lifecycle.isPetrified(), 'should not be petrified')
|
||||
})
|
||||
|
||||
context('> Initialized', () => {
|
||||
beforeEach(async () => {
|
||||
await lifecycle.initializeMock()
|
||||
})
|
||||
|
||||
it('is initialized', async () => {
|
||||
assert.isTrue(await lifecycle.hasInitialized(), 'should be initialized')
|
||||
})
|
||||
|
||||
it('is not petrified', async () => {
|
||||
assert.isFalse(await lifecycle.isPetrified(), 'should not be petrified')
|
||||
})
|
||||
|
||||
it('has correct initialization block', async () => {
|
||||
assert.equal(await lifecycle.getInitializationBlock(), await web3.eth.getBlockNumber(), 'initialization block should be correct')
|
||||
})
|
||||
|
||||
it('cannot be re-initialized', async () => {
|
||||
await assertRevert(lifecycle.initializeMock()/*, ERRORS.INIT_ALREADY_INITIALIZED*/)
|
||||
})
|
||||
|
||||
it('cannot be petrified', async () => {
|
||||
await assertRevert(lifecycle.petrifyMock()/*, ERRORS.INIT_ALREADY_INITIALIZED*/)
|
||||
})
|
||||
})
|
||||
|
||||
context('> Petrified', () => {
|
||||
beforeEach(async () => {
|
||||
await lifecycle.petrifyMock()
|
||||
})
|
||||
|
||||
it('is not initialized', async () => {
|
||||
assert.isFalse(await lifecycle.hasInitialized(), 'should not be initialized')
|
||||
})
|
||||
|
||||
it('is petrified', async () => {
|
||||
assert.isTrue(await lifecycle.isPetrified(), 'should be petrified')
|
||||
})
|
||||
|
||||
it('cannot be petrified again', async () => {
|
||||
await assertRevert(lifecycle.petrifyMock()/*, ERRORS.INIT_ALREADY_INITIALIZED*/)
|
||||
})
|
||||
|
||||
it('has initialization block in the future', async () => {
|
||||
const petrifiedBlock = await lifecycle.getInitializationBlock()
|
||||
const blockNumber = await web3.eth.getBlockNumber()
|
||||
assert.isTrue(petrifiedBlock.gt(blockNumber), 'petrified block should be in the future')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
module.exports = {
|
||||
networks: {
|
||||
// Development network is just left as truffle's default settings
|
||||
ethermint: {
|
||||
host: "127.0.0.1", // Localhost (default: none)
|
||||
port: 8545, // Standard Ethereum port (default: none)
|
||||
network_id: "*", // Any network (default: none)
|
||||
gas: 5000000, // Gas sent with each transaction
|
||||
gasPrice: 1000000000, // 1 gwei (in wei)
|
||||
},
|
||||
},
|
||||
compilers: {
|
||||
solc: {
|
||||
version: "0.4.24",
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 10000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user