From 22f771dd40960bb8fde36a1aaf801c13613d982d Mon Sep 17 00:00:00 2001 From: benjaminion Date: Fri, 14 Jul 2017 11:58:09 +0100 Subject: [PATCH] LLL: whole contract tests for the ENS Registry. --- test/boostTest.cpp | 1 + test/contracts/LLL_ENS.cpp | 506 +++++++++++++++++++++++++++++++++++++ 2 files changed, 507 insertions(+) create mode 100644 test/contracts/LLL_ENS.cpp diff --git a/test/boostTest.cpp b/test/boostTest.cpp index 0a0201879..c2121940d 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -50,6 +50,7 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) "SolidityFixedFeeRegistrar", "SolidityWallet", "LLLERC20", + "LLLENS", "LLLEndToEndTest", "GasMeterTests", "SolidityEndToEndTest", diff --git a/test/contracts/LLL_ENS.cpp b/test/contracts/LLL_ENS.cpp new file mode 100644 index 000000000..3df1546d3 --- /dev/null +++ b/test/contracts/LLL_ENS.cpp @@ -0,0 +1,506 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author Ben Edgington + * @date 2017 + * Tests for the deployed ENS Registry implementation written in LLL + */ + +#include +#include +#include +#include + +#define ACCOUNT(n) h256(account(n), h256::AlignRight) + +using namespace std; +using namespace dev::eth; + +namespace dev +{ +namespace lll +{ +namespace test +{ + +namespace +{ + +static char const* ensCode = R"DELIMITER( +;;; --------------------------------------------------------------------------- +;;; @title The Ethereum Name Service registry. +;;; @author Daniel Ellison + +(seq + + ;; -------------------------------------------------------------------------- + ;; Constant definitions. + + ;; Memory layout. + (def 'node-bytes 0x00) + (def 'label-bytes 0x20) + (def 'call-result 0x40) + + ;; Struct: Record + (def 'resolver 0x00) ; address + (def 'owner 0x20) ; address + (def 'ttl 0x40) ; uint64 + + ;; Precomputed function IDs. + (def 'get-node-owner 0x02571be3) ; owner(bytes32) + (def 'get-node-resolver 0x0178b8bf) ; resolver(bytes32) + (def 'get-node-ttl 0x16a25cbd) ; ttl(bytes32) + (def 'set-node-owner 0x5b0fc9c3) ; setOwner(bytes32,address) + (def 'set-subnode-owner 0x06ab5923) ; setSubnodeOwner(bytes32,bytes32,address) + (def 'set-node-resolver 0x1896f70a) ; setResolver(bytes32,address) + (def 'set-node-ttl 0x14ab9038) ; setTTL(bytes32,uint64) + + ;; Jumping here causes an EVM error. + (def 'invalid-location 0x02) + + ;; -------------------------------------------------------------------------- + ;; @notice Shifts the leftmost 4 bytes of a 32-byte number right by 28 bytes. + ;; @param input A 32-byte number. + + (def 'shift-right (input) + (div input (exp 2 224))) + + ;; -------------------------------------------------------------------------- + ;; @notice Determines whether the supplied function ID matches a known + ;; function hash and executes if so. + ;; @dev The function ID is in the leftmost four bytes of the call data. + ;; @param function-hash The four-byte hash of a known function signature. + ;; @param code-body The code to run in the case of a match. + + (def 'function (function-hash code-body) + (when (= (shift-right (calldataload 0x00)) function-hash) + code-body)) + + ;; -------------------------------------------------------------------------- + ;; @notice Calculates record location for the node and label passed in. + ;; @param node The parent node. + ;; @param label The hash of the subnode label. + + (def 'get-record (node label) + (seq + (mstore node-bytes node) + (mstore label-bytes label) + (sha3 node-bytes 64))) + + ;; -------------------------------------------------------------------------- + ;; @notice Retrieves owner from node record. + ;; @param node Get owner of this node. + + (def 'get-owner (node) + (sload (+ node owner))) + + ;; -------------------------------------------------------------------------- + ;; @notice Stores new owner in node record. + ;; @param node Set owner of this node. + ;; @param new-owner New owner of this node. + + (def 'set-owner (node new-owner) + (sstore (+ node owner) new-owner)) + + ;; -------------------------------------------------------------------------- + ;; @notice Stores new subnode owner in node record. + ;; @param node Set owner of this node. + ;; @param label The hash of the label specifying the subnode. + ;; @param new-owner New owner of the subnode. + + (def 'set-subowner (node label new-owner) + (sstore (+ (get-record node label) owner) new-owner)) + + ;; -------------------------------------------------------------------------- + ;; @notice Retrieves resolver from node record. + ;; @param node Get resolver of this node. + + (def 'get-resolver (node) + (sload node)) + + ;; -------------------------------------------------------------------------- + ;; @notice Stores new resolver in node record. + ;; @param node Set resolver of this node. + ;; @param new-resolver New resolver for this node. + + (def 'set-resolver (node new-resolver) + (sstore node new-resolver)) + + ;; -------------------------------------------------------------------------- + ;; @notice Retrieves TTL From node record. + ;; @param node Get TTL of this node. + + (def 'get-ttl (node) + (sload (+ node ttl))) + + ;; -------------------------------------------------------------------------- + ;; @notice Stores new TTL in node record. + ;; @param node Set TTL of this node. + ;; @param new-resolver New TTL for this node. + + (def 'set-ttl (node new-ttl) + (sstore (+ node ttl) new-ttl)) + + ;; -------------------------------------------------------------------------- + ;; @notice Checks that the caller is the node owner. + ;; @param node Check owner of this node. + + (def 'only-node-owner (node) + (when (!= (caller) (get-owner node)) + (jump invalid-location))) + + ;; -------------------------------------------------------------------------- + ;; INIT + + ;; Set the owner of the root node (0x00) to the deploying account. + (set-owner 0x00 (caller)) + + ;; -------------------------------------------------------------------------- + ;; CODE + + (returnlll + (seq + + ;; ---------------------------------------------------------------------- + ;; @notice Returns the address of the resolver for the specified node. + ;; @dev Signature: resolver(bytes32) + ;; @param node Return this node's resolver. + ;; @return The associated resolver. + + (def 'node (calldataload 0x04)) + + (function get-node-resolver + (seq + + ;; Get the node's resolver and save it. + (mstore call-result (get-resolver node)) + + ;; Return result. + (return call-result 32))) + + ;; ---------------------------------------------------------------------- + ;; @notice Returns the address that owns the specified node. + ;; @dev Signature: owner(bytes32) + ;; @param node Return this node's owner. + ;; @return The associated address. + + (def 'node (calldataload 0x04)) + + (function get-node-owner + (seq + + ;; Get the node's owner and save it. + (mstore call-result (get-owner node)) + + ;; Return result. + (return call-result 32))) + + ;; ---------------------------------------------------------------------- + ;; @notice Returns the TTL of a node and any records associated with it. + ;; @dev Signature: ttl(bytes32) + ;; @param node Return this node's TTL. + ;; @return The node's TTL. + + (def 'node (calldataload 0x04)) + + (function get-node-ttl + (seq + + ;; Get the node's TTL and save it. + (mstore call-result (get-ttl node)) + + ;; Return result. + (return call-result 32))) + + ;; ---------------------------------------------------------------------- + ;; @notice Transfers ownership of a node to a new address. May only be + ;; called by the current owner of the node. + ;; @dev Signature: setOwner(bytes32,address) + ;; @param node The node to transfer ownership of. + ;; @param new-owner The address of the new owner. + + (def 'node (calldataload 0x04)) + (def 'new-owner (calldataload 0x24)) + + (function set-node-owner + (seq (only-node-owner node) + + ;; Transfer ownership by storing passed-in address. + (set-owner node new-owner) + + ;; Emit an event about the transfer. + ;; Transfer(bytes32 indexed node, address owner); + (mstore call-result new-owner) + (log2 call-result 32 + (sha3 0x00 (lit 0x00 "Transfer(bytes32,address)")) node) + + ;; Nothing to return. + (stop))) + + ;; ---------------------------------------------------------------------- + ;; @notice Transfers ownership of a subnode to a new address. May only be + ;; called by the owner of the parent node. + ;; @dev Signature: setSubnodeOwner(bytes32,bytes32,address) + ;; @param node The parent node. + ;; @param label The hash of the label specifying the subnode. + ;; @param new-owner The address of the new owner. + + (def 'node (calldataload 0x04)) + (def 'label (calldataload 0x24)) + (def 'new-owner (calldataload 0x44)) + + (function set-subnode-owner + (seq (only-node-owner node) + + ;; Transfer ownership by storing passed-in address. + (set-subowner node label new-owner) + + ;; Emit an event about the transfer. + ;; NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); + (mstore call-result new-owner) + (log3 call-result 32 + (sha3 0x00 (lit 0x00 "NewOwner(bytes32,bytes32,address)")) + node label) + + ;; Nothing to return. + (stop))) + + ;; ---------------------------------------------------------------------- + ;; @notice Sets the resolver address for the specified node. + ;; @dev Signature: setResolver(bytes32,address) + ;; @param node The node to update. + ;; @param new-resolver The address of the resolver. + + (def 'node (calldataload 0x04)) + (def 'new-resolver (calldataload 0x24)) + + (function set-node-resolver + (seq (only-node-owner node) + + ;; Transfer ownership by storing passed-in address. + (set-resolver node new-resolver) + + ;; Emit an event about the change of resolver. + ;; NewResolver(bytes32 indexed node, address resolver); + (mstore call-result new-resolver) + (log2 call-result 32 + (sha3 0x00 (lit 0x00 "NewResolver(bytes32,address)")) node) + + ;; Nothing to return. + (stop))) + + ;; ---------------------------------------------------------------------- + ;; @notice Sets the TTL for the specified node. + ;; @dev Signature: setTTL(bytes32,uint64) + ;; @param node The node to update. + ;; @param ttl The TTL in seconds. + + (def 'node (calldataload 0x04)) + (def 'new-ttl (calldataload 0x24)) + + (function set-node-ttl + (seq (only-node-owner node) + + ;; Set new TTL by storing passed-in time. + (set-ttl node new-ttl) + + ;; Emit an event about the change of TTL. + ;; NewTTL(bytes32 indexed node, uint64 ttl); + (mstore call-result new-ttl) + (log2 call-result 32 + (sha3 0x00 (lit 0x00 "NewTTL(bytes32,uint64)")) node) + + ;; Nothing to return. + (stop))) + + ;; ---------------------------------------------------------------------- + ;; @notice Fallback: No functions matched the function ID provided. + + (jump invalid-location))) + +) +)DELIMITER"; + +static unique_ptr s_compiledEns; + +class LLLENSTestFramework: public LLLExecutionFramework +{ +protected: + void deployEns() + { + if (!s_compiledEns) + { + vector errors; + s_compiledEns.reset(new bytes(compileLLL(ensCode, false, &errors))); + BOOST_REQUIRE(errors.empty()); + } + sendMessage(*s_compiledEns, true); + BOOST_REQUIRE(!m_output.empty()); + } + +}; + +} + +// Test suite for the deployed ENS Registry implementation written in LLL +BOOST_FIXTURE_TEST_SUITE(LLLENS, LLLENSTestFramework) + +BOOST_AUTO_TEST_CASE(creation) +{ + deployEns(); + + // Root node 0x00 should initially be owned by the deploying account, account(0). + BOOST_CHECK(callContractFunction("owner(bytes32)", 0x00) == encodeArgs(ACCOUNT(0))); +} + +BOOST_AUTO_TEST_CASE(transfer_ownership) +{ + deployEns(); + + // Transfer ownership of root node from account(0) to account(1). + BOOST_REQUIRE(callContractFunction("setOwner(bytes32,address)", 0x00, ACCOUNT(1)) == encodeArgs()); + + // Check that an event was raised and contents are correct. + BOOST_REQUIRE(m_logs.size() == 1); + BOOST_CHECK(m_logs[0].data == encodeArgs(ACCOUNT(1))); + BOOST_REQUIRE(m_logs[0].topics.size() == 2); + BOOST_CHECK(m_logs[0].topics[0] == keccak256(string("Transfer(bytes32,address)"))); + BOOST_CHECK(m_logs[0].topics[1] == u256(0x00)); + + // Verify that owner of 0x00 is now account(1). + BOOST_CHECK(callContractFunction("owner(bytes32)", 0x00) == encodeArgs(ACCOUNT(1))); +} + +BOOST_AUTO_TEST_CASE(transfer_ownership_fail) +{ + deployEns(); + + // Try to steal ownership of node 0x01 + BOOST_REQUIRE(callContractFunction("setOwner(bytes32,address)", 0x01, ACCOUNT(0)) == encodeArgs()); + + // Verify that owner of 0x01 remains the default zero address + BOOST_CHECK(callContractFunction("owner(bytes32)", 0x01) == encodeArgs(0)); +} + +BOOST_AUTO_TEST_CASE(set_resolver) +{ + deployEns(); + + // Set resolver of root node to account(1). + BOOST_REQUIRE(callContractFunction("setResolver(bytes32,address)", 0x00, ACCOUNT(1)) == encodeArgs()); + + // Check that an event was raised and contents are correct. + BOOST_REQUIRE(m_logs.size() == 1); + BOOST_CHECK(m_logs[0].data == encodeArgs(ACCOUNT(1))); + BOOST_REQUIRE(m_logs[0].topics.size() == 2); + BOOST_CHECK(m_logs[0].topics[0] == keccak256(string("NewResolver(bytes32,address)"))); + BOOST_CHECK(m_logs[0].topics[1] == u256(0x00)); + + // Verify that the resolver is changed to account(1). + BOOST_CHECK(callContractFunction("resolver(bytes32)", 0x00) == encodeArgs(ACCOUNT(1))); +} + +BOOST_AUTO_TEST_CASE(set_resolver_fail) +{ + deployEns(); + + // Try to set resolver of node 0x01, which is not owned by account(0). + BOOST_REQUIRE(callContractFunction("setResolver(bytes32,address)", 0x01, ACCOUNT(0)) == encodeArgs()); + + // Verify that the resolver of 0x01 remains default zero address. + BOOST_CHECK(callContractFunction("resolver(bytes32)", 0x01) == encodeArgs(0)); +} + +BOOST_AUTO_TEST_CASE(set_ttl) +{ + deployEns(); + + // Set ttl of root node to 3600. + BOOST_REQUIRE(callContractFunction("setTTL(bytes32,uint64)", 0x00, 3600) == encodeArgs()); + + // Check that an event was raised and contents are correct. + BOOST_REQUIRE(m_logs.size() == 1); + BOOST_CHECK(m_logs[0].data == encodeArgs(3600)); + BOOST_REQUIRE(m_logs[0].topics.size() == 2); + BOOST_CHECK(m_logs[0].topics[0] == keccak256(string("NewTTL(bytes32,uint64)"))); + BOOST_CHECK(m_logs[0].topics[1] == u256(0x00)); + + // Verify that the TTL has been set. + BOOST_CHECK(callContractFunction("ttl(bytes32)", 0x00) == encodeArgs(3600)); +} + +BOOST_AUTO_TEST_CASE(set_ttl_fail) +{ + deployEns(); + + // Try to set TTL of node 0x01, which is not owned by account(0). + BOOST_REQUIRE(callContractFunction("setTTL(bytes32,uint64)", 0x01, 3600) == encodeArgs()); + + // Verify that the TTL of node 0x01 has not changed from the default. + BOOST_CHECK(callContractFunction("ttl(bytes32)", 0x01) == encodeArgs(0)); +} + +BOOST_AUTO_TEST_CASE(create_subnode) +{ + deployEns(); + + // Set ownership of "eth" sub-node to account(1) + BOOST_REQUIRE(callContractFunction("setSubnodeOwner(bytes32,bytes32,address)", 0x00, keccak256(string("eth")), ACCOUNT(1)) == encodeArgs()); + + // Check that an event was raised and contents are correct. + BOOST_REQUIRE(m_logs.size() == 1); + BOOST_CHECK(m_logs[0].data == encodeArgs(ACCOUNT(1))); + BOOST_REQUIRE(m_logs[0].topics.size() == 3); + BOOST_CHECK(m_logs[0].topics[0] == keccak256(string("NewOwner(bytes32,bytes32,address)"))); + BOOST_CHECK(m_logs[0].topics[1] == u256(0x00)); + BOOST_CHECK(m_logs[0].topics[2] == keccak256(string("eth"))); + + // Verify that the sub-node owner is now account(1). + u256 namehash = keccak256(h256(0x00).asBytes() + keccak256("eth").asBytes()); + BOOST_CHECK(callContractFunction("owner(bytes32)", namehash) == encodeArgs(ACCOUNT(1))); +} + +BOOST_AUTO_TEST_CASE(create_subnode_fail) +{ + deployEns(); + + // Send account(1) some ether for gas. + sendEther(account(1), 1000 * ether); + BOOST_REQUIRE(balanceAt(account(1)) >= 1000 * ether); + + // account(1) tries to set ownership of the "eth" sub-node. + m_sender = account(1); + BOOST_REQUIRE(callContractFunction("setSubnodeOwner(bytes32,bytes32,address)", 0x00, keccak256(string("eth")), ACCOUNT(1)) == encodeArgs()); + + // Verify that the sub-node owner remains at default zero address. + u256 namehash = keccak256(h256(0x00).asBytes() + keccak256("eth").asBytes()); + BOOST_CHECK(callContractFunction("owner(bytes32)", namehash) == encodeArgs(0)); +} + +BOOST_AUTO_TEST_CASE(fallback) +{ + deployEns(); + + // Call fallback - should just abort via jump to invalid location. + BOOST_CHECK(callFallback() == encodeArgs()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces