diff --git a/Dockerfile b/Dockerfile index efae1a7..7629984 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,8 @@ RUN make test-integration # Simulation tests RUN make test-sim-benchmark-simulation RUN make test-sim-full-app-fast +# Solidity tests +RUN make test-solidity RUN touch /test.lock diff --git a/tests/solidity/init-node.sh b/tests/solidity/init-node.sh index 1264b30..4a8fc33 100755 --- a/tests/solidity/init-node.sh +++ b/tests/solidity/init-node.sh @@ -62,12 +62,18 @@ USER3_MNEMONIC="will wear settle write dance topic tape sea glory hotel oppose r USER4_KEY="user4" USER4_MNEMONIC="doll midnight silk carpet brush boring pluck office gown inquiry duck chief aim exit gain never tennis crime fragile ship cloud surface exotic patch" +SOLIDITY_KEY="solidity" +SOLIDITY_MNEMONIC="exercise green picture marriage cause bike credit electric elephant someone march civil radio spoon sail vacant crime man fat save inject into grab drill" + # Import keys from mnemonics echo "$VAL_MNEMONIC" | "$BINARY" keys add "$VAL_KEY" --recover --keyring-backend "$KEYRING" --algo "$KEYALGO" --home "$CHAINDIR" echo "$USER1_MNEMONIC" | "$BINARY" keys add "$USER1_KEY" --recover --keyring-backend "$KEYRING" --algo "$KEYALGO" --home "$CHAINDIR" echo "$USER2_MNEMONIC" | "$BINARY" keys add "$USER2_KEY" --recover --keyring-backend "$KEYRING" --algo "$KEYALGO" --home "$CHAINDIR" echo "$USER3_MNEMONIC" | "$BINARY" keys add "$USER3_KEY" --recover --keyring-backend "$KEYRING" --algo "$KEYALGO" --home "$CHAINDIR" echo "$USER4_MNEMONIC" | "$BINARY" keys add "$USER4_KEY" --recover --keyring-backend "$KEYRING" --algo "$KEYALGO" --home "$CHAINDIR" +if ! "$BINARY" keys show "$SOLIDITY_KEY" --home "$CHAINDIR" >/dev/null 2>&1; then + echo "$SOLIDITY_MNEMONIC" | "$BINARY" keys add "$SOLIDITY_KEY" --recover --algo "$KEYALGO" --home "$CHAINDIR" +fi # Set moniker and chain-id for Cosmos EVM (Moniker can be anything, chain-id must be an integer) "$BINARY" init "$MONIKER" --chain-id "$CHAINID" --home "$CHAINDIR" @@ -114,6 +120,7 @@ sed -i.bak 's/create_empty_blocks = true/create_empty_blocks = false/g' "$CONFIG "$BINARY" add-genesis-account "$("$BINARY" keys show "$USER2_KEY" -a --keyring-backend "$KEYRING" --home "$CHAINDIR")" 1000000apoa,1000000000000000000000000000token --keyring-backend "$KEYRING" --home "$CHAINDIR" "$BINARY" add-genesis-account "$("$BINARY" keys show "$USER3_KEY" -a --keyring-backend "$KEYRING" --home "$CHAINDIR")" 1000000apoa,1000000000000000000000000000token --keyring-backend "$KEYRING" --home "$CHAINDIR" "$BINARY" add-genesis-account "$("$BINARY" keys show "$USER4_KEY" -a --keyring-backend "$KEYRING" --home "$CHAINDIR")" 1000000apoa,1000000000000000000000000000token --keyring-backend "$KEYRING" --home "$CHAINDIR" +"$BINARY" add-genesis-account "$("$BINARY" keys show "$SOLIDITY_KEY" -a --home "$CHAINDIR")" 1000000apoa,1000000000000000000000000000token --home "$CHAINDIR" # set custom pruning settings if [ "$PRUNING" = "custom" ]; then diff --git a/tests/solidity/suites/opcodes/contracts/CancunOpCodes.sol b/tests/solidity/suites/opcodes/contracts/CancunOpCodes.sol new file mode 100644 index 0000000..0e59866 --- /dev/null +++ b/tests/solidity/suites/opcodes/contracts/CancunOpCodes.sol @@ -0,0 +1,56 @@ +pragma solidity >=0.8.24; + +contract CancunOpCodes { + + // Add a todo to the list + function test() public { + + // blobbasefee + assembly { pop(blobbasefee()) } + + // mcopy test + assembly { + // Allocate memory for source and destination + let src := mload(0x40) + let dest := add(src, 0x40) + // Store a value at src + mstore(src, 0x123456789abcdef0) + // mcopy(dest, src, 32) + mcopy(dest, src, 32) + // Check that the value was copied + if iszero(eq(mload(dest), 0x123456789abcdef0)) { + revert(0, 0) + } + } + + // tstore and tload test (transient storage) + assembly { + let key := 0x42 + let val := 0xdeadbeef + tstore(key, val) + let loaded := tload(key) + if iszero(eq(loaded, val)) { + revert(0, 0) + } + } + } + + function test_revert() public { + + //revert + assembly{ revert(0, 0) } + } + + function test_invalid() public { + + //revert + assembly{ invalid() } + } + + function test_stop() public { + + //revert + assembly{ stop() } + } + +} diff --git a/tests/solidity/suites/opcodes/contracts/Migrations.sol b/tests/solidity/suites/opcodes/contracts/Migrations.sol new file mode 100644 index 0000000..42fbe88 --- /dev/null +++ b/tests/solidity/suites/opcodes/contracts/Migrations.sol @@ -0,0 +1,23 @@ +pragma solidity >=0.8.24; + +contract Migrations { + address public owner; + uint public last_completed_migration; + + constructor() public { + owner = msg.sender; + } + + modifier restricted() { + if (msg.sender == owner) _; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } + + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} diff --git a/tests/solidity/suites/opcodes/contracts/PragueOpCodes.sol b/tests/solidity/suites/opcodes/contracts/PragueOpCodes.sol new file mode 100644 index 0000000..e2ca75c --- /dev/null +++ b/tests/solidity/suites/opcodes/contracts/PragueOpCodes.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity >= 0.8.24; + +library BLS12381 { + address constant G1ADD = address(0x0b); + address constant G1MSM = address(0x0c); + address constant G2ADD = address(0x0d); + address constant G2MSM = address(0x0e); + address constant PAIRING = address(0x0f); + address constant MAP_G1 = address(0x10); + address constant MAP_G2 = address(0x11); + + function _pcall(address precompile, bytes memory input, uint outSize) + private view returns (bool ok, bytes memory out) + { + out = new bytes(outSize); + assembly { + ok := staticcall(gas(), precompile, add(input, 0x20), mload(input), add(out, 0x20), outSize) + } + } + + function mapToG1(bytes memory fp) internal view returns (bytes memory g1) { + (bool ok, bytes memory out) = _pcall(MAP_G1, fp, 128); + require(ok, "mapToG1 fail"); return out; + } + + function g1Add(bytes memory a, bytes memory b) internal view returns (bytes memory sum) { + bytes memory inbuf = bytes.concat(a, b); // 128 + 128 = 256 + (bool ok, bytes memory out) = _pcall(G1ADD, inbuf, 128); + require(ok, "g1Add fail"); return out; + } + + function pairing(bytes memory inbuf) internal view returns (bool) { + (bool ok, bytes memory out) = _pcall(PAIRING, inbuf, 32); + require(ok, "pairing fail"); + return out[31] == 0x01; + } +} + +contract PragueOpCodes { + using BLS12381 for bytes; + + // 64 zero bytes (a valid Fp element) + bytes constant FP_ZERO = hex"0000000000000000000000000000000000000000000000000000000000000000"; + + // 128 zero bytes (G1/G2 infinity encodings) + bytes constant G1_INFINITY = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + bytes constant G2_INFINITY = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; // 256B + + function test() public { + // BLS12381 + bytes memory P = BLS12381.mapToG1(FP_ZERO); + bytes memory S = BLS12381.g1Add(P, G1_INFINITY); + require(keccak256(S) == keccak256(P), "error"); + + bytes memory inbuf = bytes.concat(G1_INFINITY, G2_INFINITY); + require(BLS12381.pairing(inbuf), "error pairing"); + } + + function test_revert() public { + + //revert + assembly{ revert(0, 0) } + } + + function test_invalid() public { + + //revert + assembly{ invalid() } + } + + function test_stop() public { + + //revert + assembly{ stop() } + } +} diff --git a/tests/solidity/suites/opcodes/migrations/1_initial_migration.js b/tests/solidity/suites/opcodes/migrations/1_initial_migration.js new file mode 100644 index 0000000..a1aa22a --- /dev/null +++ b/tests/solidity/suites/opcodes/migrations/1_initial_migration.js @@ -0,0 +1,7 @@ +/* eslint-disable no-undef */ + +const Migrations = artifacts.require('Migrations') + +module.exports = function (deployer) { + deployer.deploy(Migrations) +} diff --git a/tests/solidity/suites/opcodes/migrations/2_opCodes_migration.js b/tests/solidity/suites/opcodes/migrations/2_opCodes_migration.js new file mode 100644 index 0000000..bbffa1e --- /dev/null +++ b/tests/solidity/suites/opcodes/migrations/2_opCodes_migration.js @@ -0,0 +1,9 @@ +/* eslint-disable no-undef */ + +const CancunOpCodes = artifacts.require('./CancunOpCodes.sol') +const PragueOpCodes = artifacts.require('./PragueOpCodes.sol') + +module.exports = async function (deployer) { + await deployer.deploy(CancunOpCodes) + await deployer.deploy(PragueOpCodes) +} diff --git a/tests/solidity/suites/opcodes/package.json b/tests/solidity/suites/opcodes/package.json new file mode 100644 index 0000000..a20d8f8 --- /dev/null +++ b/tests/solidity/suites/opcodes/package.json @@ -0,0 +1,26 @@ +{ + "name": "opcodes", + "version": "1.0.0", + "author": "Go Ethereum", + "license": "GPL-3.0-or-later", + "scripts": { + "test-ganache": "yarn truffle test", + "test-cosmos": "yarn truffle test --network cosmos" + }, + "devDependencies": { + "truffle-assertions": "^0.9.2" + }, + "standard": { + "globals": [ + "artifacts", + "expect", + "contract", + "beforeEach", + "before", + "web3", + "it", + "assert", + "describe" + ] + } +} diff --git a/tests/solidity/suites/opcodes/test/cancunOpCodes.js b/tests/solidity/suites/opcodes/test/cancunOpCodes.js new file mode 100644 index 0000000..ce1322f --- /dev/null +++ b/tests/solidity/suites/opcodes/test/cancunOpCodes.js @@ -0,0 +1,41 @@ +/* eslint-disable no-undef */ +/* eslint-disable no-unused-expressions */ + +const TodoList = artifacts.require('./CancunOpCodes.sol') +let contractInstance + +contract('CancunOpCodes.sol', () => { + beforeEach(async () => { + contractInstance = await TodoList.deployed() + }) + it('Should run the majority of opcodes without errors', async () => { + let error + try { + await contractInstance.test() + await contractInstance.test_stop() + } catch (err) { + error = err + } + expect(error).to.be.undefined + }) + + it('Should throw invalid op code', async () => { + let error + try { + await contractInstance.test_invalid() + } catch (err) { + error = err + } + expect(error).not.to.be.undefined + }) + + it('Should revert', async () => { + let error + try { + await contractInstance.test_revert() + } catch (err) { + error = err + } + expect(error).not.to.be.undefined + }) +}) diff --git a/tests/solidity/suites/opcodes/test/pragueOpCodes.js b/tests/solidity/suites/opcodes/test/pragueOpCodes.js new file mode 100644 index 0000000..17cd077 --- /dev/null +++ b/tests/solidity/suites/opcodes/test/pragueOpCodes.js @@ -0,0 +1,41 @@ +/* eslint-disable no-undef */ +/* eslint-disable no-unused-expressions */ + +const TodoList = artifacts.require('./PragueOpCodes.sol') +let contractInstance + +contract('PragueOpCodes.sol', () => { + beforeEach(async () => { + contractInstance = await TodoList.deployed() + }) + it('Should run the majority of opcodes without errors', async () => { + let error + try { + await contractInstance.test() + await contractInstance.test_stop() + } catch (err) { + error = err + } + expect(error).to.be.undefined + }) + + it('Should throw invalid op code', async () => { + let error + try { + await contractInstance.test_invalid() + } catch (err) { + error = err + } + expect(error).not.to.be.undefined + }) + + it('Should revert', async () => { + let error + try { + await contractInstance.test_revert() + } catch (err) { + error = err + } + expect(error).not.to.be.undefined + }) +}) diff --git a/tests/solidity/suites/opcodes/truffle-config.js b/tests/solidity/suites/opcodes/truffle-config.js new file mode 100644 index 0000000..cff7848 --- /dev/null +++ b/tests/solidity/suites/opcodes/truffle-config.js @@ -0,0 +1,28 @@ +module.exports = { + networks: { + // Development network is just left as truffle's default settings + cosmos: { + host: '127.0.0.1', // Localhost (default: none) + port: 8545, // Standard Ethereum port (default: none) + network_id: 1440002, // Any network (default: none) + gas: 8000000, // Increased gas limit for complex contracts + gasPrice: 0, // Set to 0 for test network + from: "0x5A7E818D849D4926CD2E2E05B8E934D05EE87A7C", // Address derived from the private key + accounts: [ + "0x36DC1E881F351CFE35B79E3ED27C8EE737DFD7B48A9F2D43887E25D2F87625CB" + ] + } + }, + compilers: { + solc: { + version: '0.8.24', + settings: { + evmVersion: 'cancun', + optimizer: { + enabled: false + }, + viaIR: false + } + } + } +} diff --git a/tests/solidity/yarn.lock b/tests/solidity/yarn.lock index b5bb5dd..b0d476e 100644 --- a/tests/solidity/yarn.lock +++ b/tests/solidity/yarn.lock @@ -9400,6 +9400,14 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +truffle-assertions@^0.9.2: + version "0.9.2" + resolved "https://registry.yarnpkg.com/truffle-assertions/-/truffle-assertions-0.9.2.tgz#0f8360f53ad92b6d8fdb8ceb5dce54c1fc392e23" + integrity sha512-9g2RhaxU2F8DeWhqoGQvL/bV8QVoSnQ6PY+ZPvYRP5eF7+/8LExb4mjLx/FeliLTjc3Tv1SABG05Gu5qQ/ErmA== + dependencies: + assertion-error "^1.1.0" + lodash.isequal "^4.5.0" + truffle@5.5.8: version "5.5.8" resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.5.8.tgz#79346b8478d87de1962b57c44dcd057658253716"