diff options
author | Alex Beregszaszi <alex@rtfs.hu> | 2018-11-13 22:34:37 +0800 |
---|---|---|
committer | Alex Beregszaszi <alex@rtfs.hu> | 2018-11-14 07:51:17 +0800 |
commit | 00cb5dbd43eb07b307258865dfa5abe72a3926a5 (patch) | |
tree | 447a1b529cd5540cb596817cca5da5728f3bebd6 /test/contracts | |
parent | 6961899cb2a0abd4d236a9a6f2657b4533720484 (diff) | |
download | dexon-solidity-00cb5dbd43eb07b307258865dfa5abe72a3926a5.tar.gz dexon-solidity-00cb5dbd43eb07b307258865dfa5abe72a3926a5.tar.zst dexon-solidity-00cb5dbd43eb07b307258865dfa5abe72a3926a5.zip |
Move LLL tests into a single directory
Diffstat (limited to 'test/contracts')
-rw-r--r-- | test/contracts/LLL_ENS.cpp | 507 | ||||
-rw-r--r-- | test/contracts/LLL_ERC20.cpp | 656 |
2 files changed, 0 insertions, 1163 deletions
diff --git a/test/contracts/LLL_ENS.cpp b/test/contracts/LLL_ENS.cpp deleted file mode 100644 index cfd6970c..00000000 --- a/test/contracts/LLL_ENS.cpp +++ /dev/null @@ -1,507 +0,0 @@ -/* - 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 <http://www.gnu.org/licenses/>. -*/ -/** - * @author Ben Edgington <ben@benjaminion.xyz> - * @date 2017 - * Tests for the deployed ENS Registry implementation written in LLL - */ - -#include <string> -#include <boost/test/unit_test.hpp> -#include <test/liblll/ExecutionFramework.h> -#include <liblll/Compiler.h> - -#define ACCOUNT(n) h256(account(n), h256::AlignRight) - -using namespace std; -using namespace dev::lll; - -namespace dev -{ -namespace lll -{ -namespace test -{ - -namespace -{ - -static char const* ensCode = R"DELIMITER( -;;; --------------------------------------------------------------------------- -;;; @title The Ethereum Name Service registry. -;;; @author Daniel Ellison <daniel@syrinx.net> - -(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 <code-body> 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<bytes> s_compiledEns; - -class LLLENSTestFramework: public LLLExecutionFramework -{ -protected: - void deployEns() - { - if (!s_compiledEns) - { - vector<string> errors; - s_compiledEns.reset(new bytes(compileLLL(ensCode, dev::test::Options::get().evmVersion(), dev::test::Options::get().optimize, &errors))); - BOOST_REQUIRE(errors.empty()); - } - sendMessage(*s_compiledEns, true); - BOOST_REQUIRE(m_transactionSuccessful); - 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 diff --git a/test/contracts/LLL_ERC20.cpp b/test/contracts/LLL_ERC20.cpp deleted file mode 100644 index 6c6762dd..00000000 --- a/test/contracts/LLL_ERC20.cpp +++ /dev/null @@ -1,656 +0,0 @@ -/* - 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 <http://www.gnu.org/licenses/>. -*/ -/** - * @author Ben Edgington <ben@benjaminion.xyz> - * @date 2017 - * Tests for an ERC20 token implementation written in LLL - */ - -#include <string> -#include <boost/test/unit_test.hpp> -#include <test/liblll/ExecutionFramework.h> -#include <liblll/Compiler.h> - -#define TOKENSUPPLY 100000 -#define TOKENDECIMALS 2 -#define TOKENSYMBOL "BEN" -#define TOKENNAME "Ben Token" -#define ACCOUNT(n) h256(account(n), h256::AlignRight) -#define SUCCESS encodeArgs(1) - -using namespace std; -using namespace dev::lll; - -namespace dev -{ -namespace lll -{ -namespace test -{ - -namespace -{ - -static char const* erc20Code = R"DELIMITER( -(seq - - ;; -------------------------------------------------------------------------- - ;; CONSTANTS - - ;; Token parameters. - ;; 0x40 is a "magic number" - the text of the string is placed here - ;; when returning the string to the caller. See return-string below. - (def 'token-name-string (lit 0x40 "Ben Token")) - (def 'token-symbol-string (lit 0x40 "BEN")) - (def 'token-decimals 2) - (def 'token-supply 100000) ; 1000.00 total tokens - - ;; Booleans - (def 'false 0) - (def 'true 1) - - ;; Memory layout. - (def 'mem-ret 0x00) ; Fixed due to compiler macro for return. - (def 'mem-func 0x00) ; No conflict with mem-ret, so re-use. - (def 'mem-keccak 0x00) ; No conflict with mem-func or mem-ret, so re-use. - (def 'scratch0 0x20) - (def 'scratch1 0x40) - - ;; Precomputed function IDs. - (def 'get-name 0x06fdde03) ; name() - (def 'get-symbol 0x95d89b41) ; symbol() - (def 'get-decimals 0x313ce567) ; decimals() - (def 'get-total-supply 0x18160ddd) ; totalSupply() - (def 'get-balance-of 0x70a08231) ; balanceOf(address) - (def 'transfer 0xa9059cbb) ; transfer(address,uint256) - (def 'transfer-from 0x23b872dd) ; transferFrom(address,address,uint256) - (def 'approve 0x095ea7b3) ; approve(address,uint256) - (def 'get-allowance 0xdd62ed3e) ; allowance(address,address) - - ;; Event IDs - (def 'transfer-event-id ; Transfer(address,address,uint256) - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef) - - (def 'approval-event-id ; Approval(address,address,uint256) - 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925) - - ;; -------------------------------------------------------------------------- - ;; UTILITIES - - ;; -------------------------------------------------------------------------- - ;; The following define the key data-structures: - ;; - balance(addr) => value - ;; - allowance(addr,addr) => value - - ;; Balances are stored at s[owner_addr]. - (def 'balance (address) address) - - ;; Allowances are stored at s[owner_addr + keccak256(spender_addr)] - ;; We use a crypto function here to avoid any situation where - ;; approve(me, spender) can be abused to do approve(target, me). - (def 'allowance (owner spender) - (seq - (mstore mem-keccak spender) - (keccak256 mem-keccak 0x20))) - - ;; -------------------------------------------------------------------------- - ;; For convenience we have macros to refer to function arguments - - (def 'arg1 (calldataload 0x04)) - (def 'arg2 (calldataload 0x24)) - (def 'arg3 (calldataload 0x44)) - - ;; -------------------------------------------------------------------------- - ;; Revert is a soft return that does not consume the remaining gas. - ;; We use it when rejecting invalid user input. - ;; - ;; Note: The REVERT opcode will be implemented in Metropolis (EIP 140). - ;; Meanwhile it just causes an invalid instruction exception (similar - ;; to a "throw" in Solidity). When fully implemented, Revert could be - ;; use to return error codes, or even messages. - - (def 'revert () (revert 0 0)) - - ;; -------------------------------------------------------------------------- - ;; Macro for returning string names. - ;; Compliant with the ABI format for strings. - - (def 'return-string (string-literal) - (seq - (mstore 0x00 0x20) ; Points to our string's memory location - (mstore 0x20 string-literal) ; Length. String itself is copied to 0x40. - (return 0x00 (& (+ (mload 0x20) 0x5f) (~ 0x1f))))) - ; Round return up to 32 byte boundary - - ;; -------------------------------------------------------------------------- - ;; Convenience macro for raising Events - - (def 'event3 (id addr1 addr2 value) - (seq - (mstore scratch0 value) - (log3 scratch0 0x20 id addr1 addr2))) - - ;; -------------------------------------------------------------------------- - ;; Determines whether the stored function ID matches a known - ;; function hash and executes <code-body> if so. - ;; @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 (= (mload mem-func) function-hash) - code-body)) - - ;; -------------------------------------------------------------------------- - ;; Gets the function ID and stores it in memory for reference. - ;; The function ID is in the leftmost four bytes of the call data. - - (def 'uses-functions - (mstore - mem-func - (shr (calldataload 0x00) 224))) - - ;; -------------------------------------------------------------------------- - ;; GUARDS - - ;; -------------------------------------------------------------------------- - ;; Checks that ensure that each function is called with the right - ;; number of arguments. For one thing this addresses the "ERC20 - ;; short address attack". For another, it stops me making - ;; mistakes while testing. We use these only on the non-constant functions. - - (def 'has-one-arg (unless (= 0x24 (calldatasize)) (revert))) - (def 'has-two-args (unless (= 0x44 (calldatasize)) (revert))) - (def 'has-three-args (unless (= 0x64 (calldatasize)) (revert))) - - ;; -------------------------------------------------------------------------- - ;; Check that addresses have only 160 bits and revert if not. - ;; We use these input type-checks on the non-constant functions. - - (def 'is-address (addr) - (when - (shr addr 160) - (revert))) - - ;; -------------------------------------------------------------------------- - ;; Check that transfer values are smaller than total supply and - ;; revert if not. This should effectively exclude negative values. - - (def 'is-value (value) - (when (> value token-supply) (revert))) - - ;; -------------------------------------------------------------------------- - ;; Will revert if sent any Ether. We use the macro immediately so as - ;; to abort if sent any Ether during contract deployment. - - (def 'not-payable - (when (callvalue) (revert))) - - not-payable - - ;; -------------------------------------------------------------------------- - ;; INITIALISATION - ;; - ;; Assign all tokens initially to the owner of the contract. - - (sstore (balance (caller)) token-supply) - - ;; -------------------------------------------------------------------------- - ;; CONTRACT CODE - - (returnlll - (seq not-payable uses-functions - - ;; ---------------------------------------------------------------------- - ;; Getter for the name of the token. - ;; @abi name() constant returns (string) - ;; @return The token name as a string. - - (function get-name - (return-string token-name-string)) - - ;; ---------------------------------------------------------------------- - ;; Getter for the symbol of the token. - ;; @abi symbol() constant returns (string) - ;; @return The token symbol as a string. - - (function get-symbol - (return-string token-symbol-string)) - - ;; ---------------------------------------------------------------------- - ;; Getter for the number of decimals assigned to the token. - ;; @abi decimals() constant returns (uint256) - ;; @return The token decimals. - - (function get-decimals - (return token-decimals)) - - ;; ---------------------------------------------------------------------- - ;; Getter for the total token supply. - ;; @abi totalSupply() constant returns (uint256) - ;; @return The token supply. - - (function get-total-supply - (return token-supply)) - - ;; ---------------------------------------------------------------------- - ;; Returns the account balance of another account. - ;; @abi balanceOf(address) constant returns (uint256) - ;; @param owner The address of the account's owner. - ;; @return The account balance. - - (function get-balance-of - (seq - - (def 'owner arg1) - - (return (sload (balance owner))))) - - ;; ---------------------------------------------------------------------- - ;; Transfers _value amount of tokens to address _to. The command - ;; should throw if the _from account balance has not enough - ;; tokens to spend. - ;; @abi transfer(address, uint256) returns (bool) - ;; @param to The account to receive the tokens. - ;; @param value The quantity of tokens to transfer. - ;; @return Success (true). Other outcomes result in a Revert. - - (function transfer - (seq has-two-args (is-address arg1) (is-value arg2) - - (def 'to arg1) - (def 'value arg2) - - (when value ; value == 0 is a no-op - (seq - - ;; The caller's balance. Save in memory for efficiency. - (mstore scratch0 (sload (balance (caller)))) - - ;; Revert if the caller's balance is not sufficient. - (when (> value (mload scratch0)) - (revert)) - - ;; Make the transfer - ;; It would be good to check invariants (sum of balances). - (sstore (balance (caller)) (- (mload scratch0) value)) - (sstore (balance to) (+ (sload (balance to)) value)) - - ;; Event - Transfer(address,address,uint256) - (event3 transfer-event-id (caller) to value))) - - (return true))) - - ;; ---------------------------------------------------------------------- - ;; Send _value amount of tokens from address _from to address _to - ;; @abi transferFrom(address,address,uint256) returns (bool) - ;; @param from The account to send the tokens from. - ;; @param to The account to receive the tokens. - ;; @param value The quantity of tokens to transfer. - ;; @return Success (true). Other outcomes result in a Revert. - - (function transfer-from - (seq has-three-args (is-address arg1) (is-address arg2) (is-value arg3) - - (def 'from arg1) - (def 'to arg2) - (def 'value arg3) - - (when value ; value == 0 is a no-op - - (seq - - ;; Save data to memory for efficiency. - (mstore scratch0 (sload (balance from))) - (mstore scratch1 (sload (allowance from (caller)))) - - ;; Revert if not enough funds, or not enough approved. - (when - (|| - (> value (mload scratch0)) - (> value (mload scratch1))) - (revert)) - - ;; Make the transfer and update allowance. - (sstore (balance from) (- (mload scratch0) value)) - (sstore (balance to) (+ (sload (balance to)) value)) - (sstore (allowance from (caller)) (- (mload scratch1) value)) - - ;; Event - Transfer(address,address,uint256) - (event3 transfer-event-id from to value))) - - (return true))) - - ;; ---------------------------------------------------------------------- - ;; Allows _spender to withdraw from your account multiple times, - ;; up to the _value amount. If this function is called again it - ;; overwrites the current allowance with _value. - ;; @abi approve(address,uint256) returns (bool) - ;; @param spender The withdrawing account having its limit set. - ;; @param value The maximum allowed amount. - ;; @return Success (true). Other outcomes result in a Revert. - - (function approve - (seq has-two-args (is-address arg1) (is-value arg2) - - (def 'spender arg1) - (def 'value arg2) - - ;; Force users set the allowance to 0 before setting it to - ;; another value for the same spender. Prevents this attack: - ;; https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM - (when - (&& value (sload (allowance (caller) spender))) - (revert)) - - (sstore (allowance (caller) spender) value) - - ;; Event - Approval(address,address,uint256) - (event3 approval-event-id (caller) spender value) - - (return true))) - - ;; ---------------------------------------------------------------------- - ;; Returns the amount which _spender is still allowed to withdraw - ;; from _owner. - ;; @abi allowance(address,address) constant returns (uint256) - ;; @param owner The owning account. - ;; @param spender The withdrawing account. - ;; @return The allowed amount remaining. - - (function get-allowance - (seq - - (def 'owner arg1) - (def 'spender arg2) - - (return (sload (allowance owner spender))))) - - ;; ---------------------------------------------------------------------- - ;; Fallback: No functions matched the function ID provided. - - (revert))) - ) -)DELIMITER"; - -static unique_ptr<bytes> s_compiledErc20; - -class LLLERC20TestFramework: public LLLExecutionFramework -{ -protected: - void deployErc20() - { - if (!s_compiledErc20) - { - vector<string> errors; - s_compiledErc20.reset(new bytes(compileLLL(erc20Code, dev::test::Options::get().evmVersion(), dev::test::Options::get().optimize, &errors))); - BOOST_REQUIRE(errors.empty()); - } - sendMessage(*s_compiledErc20, true); - BOOST_REQUIRE(m_transactionSuccessful); - BOOST_REQUIRE(!m_output.empty()); - } - -}; - -} - -// Test suite for an ERC20 contract written in LLL. -BOOST_FIXTURE_TEST_SUITE(LLLERC20, LLLERC20TestFramework) - -BOOST_AUTO_TEST_CASE(creation) -{ - deployErc20(); - - // All tokens are initially assigned to the contract creator. - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY)); -} - -BOOST_AUTO_TEST_CASE(constants) -{ - deployErc20(); - - BOOST_CHECK(callContractFunction("totalSupply()") == encodeArgs(TOKENSUPPLY)); - BOOST_CHECK(callContractFunction("decimals()") == encodeArgs(TOKENDECIMALS)); - BOOST_CHECK(callContractFunction("symbol()") == encodeDyn(string(TOKENSYMBOL))); - BOOST_CHECK(callContractFunction("name()") == encodeDyn(string(TOKENNAME))); -} - -BOOST_AUTO_TEST_CASE(send_value) -{ - deployErc20(); - - // Send value to the contract. Should always fail. - m_sender = account(0); - auto contractBalance = balanceAt(m_contractAddress); - - // Fallback: check value is not transferred. - BOOST_CHECK(callFallbackWithValue(42) != SUCCESS); - BOOST_CHECK(balanceAt(m_contractAddress) == contractBalance); - - // Transfer: check nothing happened. - BOOST_CHECK(callContractFunctionWithValue("transfer(address,uint256)", ACCOUNT(1), 100, 42) != SUCCESS); - BOOST_CHECK(balanceAt(m_contractAddress) == contractBalance); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(0)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY)); -} - -BOOST_AUTO_TEST_CASE(transfer) -{ - deployErc20(); - - // Transfer 100 tokens from account(0) to account(1). - int transfer = 100; - m_sender = account(0); - BOOST_CHECK(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) == SUCCESS); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY - transfer)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(transfer)); -} - -BOOST_AUTO_TEST_CASE(transfer_from) -{ - deployErc20(); - - // Approve account(1) to transfer up to 1000 tokens from account(0). - int allow = 1000; - m_sender = account(0); - BOOST_REQUIRE(callContractFunction("approve(address,uint256)", ACCOUNT(1), u256(allow)) == SUCCESS); - BOOST_REQUIRE(callContractFunction("allowance(address,address)", ACCOUNT(0), ACCOUNT(1)) == encodeArgs(allow)); - - // Send account(1) some ether for gas. - sendEther(account(1), 1000 * ether); - BOOST_REQUIRE(balanceAt(account(1)) >= 1000 * ether); - - // Transfer 300 tokens from account(0) to account(2); check that the allowance decreases. - int transfer = 300; - m_sender = account(1); - BOOST_REQUIRE(callContractFunction("transferFrom(address,address,uint256)", ACCOUNT(0), ACCOUNT(2), u256(transfer)) == SUCCESS); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(2)) == encodeArgs(transfer)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY - transfer)); - BOOST_CHECK(callContractFunction("allowance(address,address)", ACCOUNT(0), ACCOUNT(1)) == encodeArgs(allow - transfer)); -} - -BOOST_AUTO_TEST_CASE(transfer_event) -{ - deployErc20(); - - // Transfer 1000 tokens from account(0) to account(1). - int transfer = 1000; - m_sender = account(0); - BOOST_REQUIRE(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) == SUCCESS); - - // Check that a Transfer event was recorded and contents are correct. - BOOST_REQUIRE(m_logs.size() == 1); - BOOST_CHECK(m_logs[0].data == encodeArgs(transfer)); - BOOST_REQUIRE(m_logs[0].topics.size() == 3); - BOOST_CHECK(m_logs[0].topics[0] == keccak256(string("Transfer(address,address,uint256)"))); - BOOST_CHECK(m_logs[0].topics[1] == ACCOUNT(0)); - BOOST_CHECK(m_logs[0].topics[2] == ACCOUNT(1)); -} - -BOOST_AUTO_TEST_CASE(transfer_zero_no_event) -{ - deployErc20(); - - // Transfer 0 tokens from account(0) to account(1). This is a no-op. - int transfer = 0; - m_sender = account(0); - BOOST_REQUIRE(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) == SUCCESS); - - // Check that no Event was recorded. - BOOST_CHECK(m_logs.size() == 0); - - // Check that balances have not changed. - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY - transfer)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(transfer)); -} - -BOOST_AUTO_TEST_CASE(approval_and_transfer_events) -{ - deployErc20(); - - // Approve account(1) to transfer up to 10000 tokens from account(0). - int allow = 10000; - m_sender = account(0); - BOOST_REQUIRE(callContractFunction("approve(address,uint256)", ACCOUNT(1), u256(allow)) == SUCCESS); - - // Check that an Approval event was recorded and contents are correct. - BOOST_REQUIRE(m_logs.size() == 1); - BOOST_CHECK(m_logs[0].data == encodeArgs(allow)); - BOOST_REQUIRE(m_logs[0].topics.size() == 3); - BOOST_CHECK(m_logs[0].topics[0] == keccak256(string("Approval(address,address,uint256)"))); - BOOST_CHECK(m_logs[0].topics[1] == ACCOUNT(0)); - BOOST_CHECK(m_logs[0].topics[2] == ACCOUNT(1)); - - // Send account(1) some ether for gas. - sendEther(account(1), 1000 * ether); - BOOST_REQUIRE(balanceAt(account(1)) >= 1000 * ether); - - // Transfer 3000 tokens from account(0) to account(2); check that the allowance decreases. - int transfer = 3000; - m_sender = account(1); - BOOST_REQUIRE(callContractFunction("transferFrom(address,address,uint256)", ACCOUNT(0), ACCOUNT(2), u256(transfer)) == SUCCESS); - - // Check that a Transfer event was recorded and contents are correct. - BOOST_REQUIRE(m_logs.size() == 1); - BOOST_CHECK(m_logs[0].data == encodeArgs(transfer)); - BOOST_REQUIRE(m_logs[0].topics.size() == 3); - BOOST_CHECK(m_logs[0].topics[0] == keccak256(string("Transfer(address,address,uint256)"))); - BOOST_CHECK(m_logs[0].topics[1] == ACCOUNT(0)); - BOOST_CHECK(m_logs[0].topics[2] == ACCOUNT(2)); -} - -BOOST_AUTO_TEST_CASE(invalid_transfer_1) -{ - deployErc20(); - - // Transfer more than the total supply; ensure nothing changes. - int transfer = TOKENSUPPLY + 1; - m_sender = account(0); - BOOST_CHECK(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) != SUCCESS); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(0)); -} - -BOOST_AUTO_TEST_CASE(invalid_transfer_2) -{ - deployErc20(); - - // Separate transfers that together exceed initial balance. - int transfer = 1 + TOKENSUPPLY / 2; - m_sender = account(0); - - // First transfer should succeed. - BOOST_REQUIRE(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) == SUCCESS); - BOOST_REQUIRE(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY - transfer)); - BOOST_REQUIRE(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(transfer)); - - // Second transfer should fail. - BOOST_CHECK(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) != SUCCESS); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY - transfer)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(transfer)); -} - -BOOST_AUTO_TEST_CASE(invalid_transfer_from) -{ - deployErc20(); - - // TransferFrom without approval. - int transfer = 300; - - // Send account(1) some ether for gas. - m_sender = account(0); - sendEther(account(1), 1000 * ether); - BOOST_REQUIRE(balanceAt(account(1)) >= 1000 * ether); - - // Try the transfer; ensure nothing changes. - m_sender = account(1); - BOOST_CHECK(callContractFunction("transferFrom(address,address,uint256)", ACCOUNT(0), ACCOUNT(2), u256(transfer)) != SUCCESS); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(2)) == encodeArgs(0)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY)); - BOOST_CHECK(callContractFunction("allowance(address,address)", ACCOUNT(0), ACCOUNT(1)) == encodeArgs(0)); -} - -BOOST_AUTO_TEST_CASE(invalid_reapprove) -{ - deployErc20(); - - m_sender = account(0); - - // Approve account(1) to transfer up to 1000 tokens from account(0). - int allow1 = 1000; - BOOST_REQUIRE(callContractFunction("approve(address,uint256)", ACCOUNT(1), u256(allow1)) == SUCCESS); - BOOST_REQUIRE(callContractFunction("allowance(address,address)", ACCOUNT(0), ACCOUNT(1)) == encodeArgs(allow1)); - - // Now approve account(1) to transfer up to 500 tokens from account(0). - // Should fail (we need to reset allowance to 0 first). - int allow2 = 500; - BOOST_CHECK(callContractFunction("approve(address,uint256)", ACCOUNT(1), u256(allow2)) != SUCCESS); - BOOST_CHECK(callContractFunction("allowance(address,address)", ACCOUNT(0), ACCOUNT(1)) == encodeArgs(allow1)); -} - -BOOST_AUTO_TEST_CASE(bad_data) -{ - deployErc20(); - - m_sender = account(0); - - // Correct data: transfer(address _to, 1). - sendMessage((bytes)fromHex("a9059cbb") + (bytes)fromHex("000000000000000000000000123456789a123456789a123456789a123456789a") + encodeArgs(1), false, 0); - BOOST_CHECK(m_transactionSuccessful); - BOOST_CHECK(m_output == SUCCESS); - - // Too little data (address is truncated by one byte). - sendMessage((bytes)fromHex("a9059cbb") + (bytes)fromHex("000000000000000000000000123456789a123456789a123456789a12345678") + encodeArgs(1), false, 0); - BOOST_CHECK(!m_transactionSuccessful); - BOOST_CHECK(m_output != SUCCESS); - - // Too much data (address is extended with a zero byte). - sendMessage((bytes)fromHex("a9059cbb") + (bytes)fromHex("000000000000000000000000123456789a123456789a123456789a123456789a00") + encodeArgs(1), false, 0); - BOOST_CHECK(!m_transactionSuccessful); - BOOST_CHECK(m_output != SUCCESS); - - // Invalid address (a bit above the 160th is set). - sendMessage((bytes)fromHex("a9059cbb") + (bytes)fromHex("000000000000000000000100123456789a123456789a123456789a123456789a") + encodeArgs(1), false, 0); - BOOST_CHECK(!m_transactionSuccessful); - BOOST_CHECK(m_output != SUCCESS); -} - -BOOST_AUTO_TEST_SUITE_END() - -} -} -} // end namespaces |