2017-07-11 18:59:13 +00:00
|
|
|
/*
|
|
|
|
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::eth;
|
|
|
|
|
|
|
|
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;
|
2018-03-01 11:06:36 +00:00
|
|
|
s_compiledErc20.reset(new bytes(compileLLL(erc20Code, dev::test::Options::get().evmVersion(), dev::test::Options::get().optimize, &errors)));
|
2017-07-11 18:59:13 +00:00
|
|
|
BOOST_REQUIRE(errors.empty());
|
|
|
|
}
|
|
|
|
sendMessage(*s_compiledErc20, true);
|
|
|
|
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_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_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_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_output != SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // end namespaces
|