diff --git a/.github/workflows/tests-e2e-nix.yml b/.github/workflows/tests-e2e-nix.yml index 8b37624c..e720a85d 100644 --- a/.github/workflows/tests-e2e-nix.yml +++ b/.github/workflows/tests-e2e-nix.yml @@ -84,6 +84,13 @@ jobs: os: [macos-latest] runs-on: ${{ matrix.os }} steps: + - name: Remove existing Nix build users (macOS fix) + run: | + for i in $(seq 1 32); do + if dscl . -read /Users/_nixbld$i &>/dev/null; then + sudo dscl . -delete /Users/_nixbld$i + fi + done - uses: actions/checkout@v3 with: fetch-depth: 0 diff --git a/integration_tests/configs/ERC20.bin b/integration_tests/configs/ERC20.bin new file mode 100644 index 00000000..cbd738d6 --- /dev/null +++ b/integration_tests/configs/ERC20.bin @@ -0,0 +1 @@ +60a06040523461022357610ee28038038061001981610227565b9283398101906040818303126102235780516001600160401b0381116102235781019082601f830112156102235781516001600160401b03811161020f5761006a601f8201601f1916602001610227565b9381855260208285010111610223576020815f92828096018388015e8501015201516001600160a01b03811681036102235781516001600160401b03811161020f575f54600181811c91168015610205575b60208210146101f157601f811161018f575b50602092601f821160011461013057928192935f92610125575b50508160011b915f199060031b1c1916175f555b608052604051610c95908161024d8239608051818181610198015281816106680152610a940152f35b015190505f806100e8565b601f198216935f8052805f20915f5b868110610177575083600195961061015f575b505050811b015f556100fc565b01515f1960f88460031b161c191690555f8080610152565b9192602060018192868501518155019401920161013f565b5f80527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563601f830160051c810191602084106101e7575b601f0160051c01905b8181106101dc57506100ce565b5f81556001016101cf565b90915081906101c6565b634e487b7160e01b5f52602260045260245ffd5b90607f16906100bc565b634e487b7160e01b5f52604160045260245ffd5b5f80fd5b6040519190601f01601f191682016001600160401b0381118382101761020f5760405256fe60806040526004361015610011575f80fd5b5f3560e01c806306fdde03146100c4578063095ea7b3146100bf57806318160ddd146100ba57806323b872dd146100b5578063313ce567146100b057806370a08231146100ab57806376cdb03b146100a657806395d89b41146100a1578063a9059cbb1461009c578063c370b042146100975763dd62ed3e14610092575f80fd5b61090a565b6108d1565b610704565b61068c565b61061e565b610560565b6104d9565b6103bd565b61033a565b610216565b610112565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080809581815201938051918291828752018686015e5f8582860101520116010190565b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc576101c86101bc61016a6101966101546107c6565b6040519283915f6020840152602183019061099b565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282610780565b7f0000000000000000000000000000000000000000000000000000000000000000610b83565b604051918291826100c9565b0390f35b5f80fd5b6004359073ffffffffffffffffffffffffffffffffffffffff821682036101cc57565b6024359073ffffffffffffffffffffffffffffffffffffffff821682036101cc57565b346101cc5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc5761024d6101d0565b602435331561030e5773ffffffffffffffffffffffffffffffffffffffff82169182156102e2576102a88291335f52600160205260405f209073ffffffffffffffffffffffffffffffffffffffff165f5260205260405f2090565b5560405190815233907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92590602090a3602060405160018152f35b7f94280d62000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b7fe602df05000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc5760206103b061016a61019661037b6107c6565b6040519283917f030000000000000000000000000000000000000000000000000000000000000087840152602183019061099b565b0151604051908152602090f35b346101cc5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc576103f46101d0565b6103fc6101f3565b6044359073ffffffffffffffffffffffffffffffffffffffff83165f5260016020526104493360405f209073ffffffffffffffffffffffffffffffffffffffff165f5260205260405f2090565b54927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8410610489575b61047d93506109ad565b60405160018152602090f35b8284106104a5576104a08361047d95033383610bdb565b610473565b82847ffb8f41b2000000000000000000000000000000000000000000000000000000005f523360045260245260445260645ffd5b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc57602061054f61016a61019661051a6107c6565b6040519283917f020000000000000000000000000000000000000000000000000000000000000087840152602183019061099b565b01516040515f9190911a8152602090f35b346101cc5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc576101c8602061060c7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000006101966105c36101d0565b61016a6105ce6107c6565b6040519485937f04000000000000000000000000000000000000000000000000000000000000008986015260601b166021840152603583019061099b565b01516040519081529081906020820190565b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc576101c86101bc61016a6101966106ce6107c6565b6040519283917f01000000000000000000000000000000000000000000000000000000000000006020840152602183019061099b565b346101cc5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc5761074861073e6101d0565b60243590336109ad565b602060405160018152f35b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176107c157604052565b610753565b604051905f5f548060011c916001821680156108c7575b60208410811461089a57838652859260208401919081156108635750600114610810575b5061080e92500383610780565b565b5f80805291507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b84831061084c575061080e9350015f610801565b805482840152869350602090920191600101610838565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682525061080e93151560051b0190505f610801565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b92607f16926107dd565b346101cc575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc576101c86101bc6107c6565b346101cc5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cc5760206109926109466101d0565b73ffffffffffffffffffffffffffffffffffffffff6109636101f3565b91165f526001835260405f209073ffffffffffffffffffffffffffffffffffffffff165f5260205260405f2090565b54604051908152f35b805191908290602001825e015f815290565b9073ffffffffffffffffffffffffffffffffffffffff8216918215610afa5773ffffffffffffffffffffffffffffffffffffffff8216938415610ace57610ab87fffffffffffffffffffffffffffffffffffffffff000000000000000000000000610a92610ac99461016a7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9784610a436107c6565b916040519687957f0500000000000000000000000000000000000000000000000000000000000000602088015260601b16602186015260601b166035840152866049840152606983019061099b565b7f0000000000000000000000000000000000000000000000000000000000000000610c49565b506040519081529081906020820190565b0390a3565b7fec442f05000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b7f96c6fd1e000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b3d15610b7e573d9067ffffffffffffffff82116107c15760405191610b7360207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184610780565b82523d5f602084013e565b606090565b5f918291602082519201905afa610b98610b26565b9015610ba15790565b610bd7906040519182917f0bcb658c000000000000000000000000000000000000000000000000000000008352600483016100c9565b0390fd5b73ffffffffffffffffffffffffffffffffffffffff1690811561030e5773ffffffffffffffffffffffffffffffffffffffff8116156102e257610c46915f52600160205260405f209073ffffffffffffffffffffffffffffffffffffffff165f5260205260405f2090565b55565b5f91829182602083519301915af1610b98610b2656fea26469706673582212206c349a091a8d116c46a490fccb327923675dd9203b2a952fe99cdb4b058beab664736f6c634300081e0033 \ No newline at end of file diff --git a/integration_tests/configs/chains.jsonnet b/integration_tests/configs/chains.jsonnet index cf2b6cb7..69f7bd3c 100644 --- a/integration_tests/configs/chains.jsonnet +++ b/integration_tests/configs/chains.jsonnet @@ -15,11 +15,7 @@ evm_denom: 'uom', cmd: 'mantrachaind', evm_chain_id: 7888, - evm: { - params: { - allow_unprotected_txs: true, - }, - }, + evm: {}, feemarket: { params: { base_fee: '0.010000000000000000', diff --git a/integration_tests/configs/default.jsonnet b/integration_tests/configs/default.jsonnet index e24de4f8..adfe41b3 100644 --- a/integration_tests/configs/default.jsonnet +++ b/integration_tests/configs/default.jsonnet @@ -13,6 +13,7 @@ local chain = (import 'chains.jsonnet')[std.extVar('CHAIN_CONFIG')]; 'app-config': { evm: { 'evm-chain-id': chain.evm_chain_id, + 'block-executor': 'block-stm', }, grpc: { 'skip-check-header': true, @@ -24,7 +25,7 @@ local chain = (import 'chains.jsonnet')[std.extVar('CHAIN_CONFIG')]; enable: true, address: '127.0.0.1:{EVMRPC_PORT}', 'ws-address': '127.0.0.1:{EVMRPC_PORT_WS}', - api: 'eth,net,web3,debug', + api: 'eth,net,web3,debug,txpool', 'feehistory-cap': 100, 'block-range-cap': 10000, 'logs-cap': 10000, diff --git a/integration_tests/cosmoscli.py b/integration_tests/cosmoscli.py index 5444f0c7..d243abd5 100644 --- a/integration_tests/cosmoscli.py +++ b/integration_tests/cosmoscli.py @@ -219,18 +219,17 @@ def sign_tx_json(self, tx, signer, max_priority_price=None, **kwargs): fp.flush() return self.sign_tx(fp.name, signer, **kwargs) - def create_account(self, name, mnemonic=None, coin_type=60, **kwargs): + def create_account(self, name, mnemonic=None, **kwargs): "create new keypair in node's keyring" - if coin_type == 60: - kwargs["key_type"] = "eth_secp256k1" + if kwargs.get("coin_type", 60) == 60: + kwargs.update({"coin_type": 60, "key_type": "eth_secp256k1"}) default_kwargs = self.get_kwargs() + args = {**default_kwargs, **kwargs} if mnemonic is None: - output = self.raw( - "keys", - "add", - name, - **(default_kwargs | kwargs), - ) + if kwargs.get("source"): + output = self.raw("keys", "add", name, "--recover", **args) + else: + output = self.raw("keys", "add", name, **args) else: output = self.raw( "keys", @@ -238,7 +237,7 @@ def create_account(self, name, mnemonic=None, coin_type=60, **kwargs): name, "--recover", stdin=mnemonic.encode() + b"\n", - **(default_kwargs | kwargs), + **args, ) return json.loads(output) diff --git a/integration_tests/test_basic.py b/integration_tests/test_basic.py index 2fb99a74..b7dcbefe 100644 --- a/integration_tests/test_basic.py +++ b/integration_tests/test_basic.py @@ -3,6 +3,7 @@ import pytest import web3 +from eth_account import Account from eth_bloom import BloomFilter from eth_contract.erc20 import ERC20 from eth_contract.utils import send_transaction as send_transaction_async @@ -20,6 +21,7 @@ address_to_bytes32, assert_balance, assert_transfer, + bech32_to_eth, build_batch_tx, build_contract, contract_address, @@ -166,47 +168,32 @@ def test_connect_transaction(connect_mantra): def test_transaction(mantra, connect_mantra, diff=1): w3 = connect_mantra.w3 gas_price = w3.eth.gas_price + gas = 21000 sender = ADDRS["community"] receiver = ADDRS["signer1"] # send transaction - txhash_1 = send_transaction( - w3, - {"to": receiver, "value": 10000, "gasPrice": gas_price}, - )["transactionHash"] + data = {"to": ADDRS["community"], "value": 10000, "gasPrice": gas_price, "gas": gas} + txhash_1 = send_transaction(w3, data)["transactionHash"] tx1 = w3.eth.get_transaction(txhash_1) assert tx1["transactionIndex"] == 0 - initial_block_number = w3.eth.get_block_number() + with pytest.raises(web3.exceptions.Web3RPCError, match="tx already in mempool"): + data["nonce"] = w3.eth.get_transaction_count(sender) - 1 + send_transaction(w3, data) - # tx already in mempool - with pytest.raises(web3.exceptions.Web3RPCError) as exc: - send_transaction( - w3, - { - "to": receiver, - "value": 10000, - "gasPrice": gas_price, - "nonce": w3.eth.get_transaction_count(sender) - 1, - }, - ) - assert "tx already in mempool" in str(exc) + data["nonce"] = w3.eth.get_transaction_count(sender) + 1 + txhash = send_transaction(w3, data, check=False) - # invalid sequence - with pytest.raises(web3.exceptions.Web3RPCError) as exc: - send_transaction( - w3, - { - "to": receiver, - "value": 10000, - "gasPrice": w3.eth.gas_price, - "nonce": w3.eth.get_transaction_count(sender) + 1, - }, - ) - assert "invalid sequence" in str(exc) + data["nonce"] = w3.eth.get_transaction_count(sender) + receipt = send_transaction(w3, data) + assert receipt["status"] == 1 - # out of gas - with pytest.raises(web3.exceptions.Web3RPCError) as exc: + # tx queued due to nonce gap should be success now + receipt = w3.eth.wait_for_transaction_receipt(txhash) + assert receipt["status"] == 1 + + with pytest.raises(web3.exceptions.Web3RPCError, match="intrinsic gas too low"): send_transaction( w3, { @@ -216,22 +203,17 @@ def test_transaction(mantra, connect_mantra, diff=1): "gas": 1, }, )["transactionHash"] - assert "intrinsic gas too low" in str(exc) - # insufficient fee - with pytest.raises(web3.exceptions.Web3RPCError) as exc: + with pytest.raises(web3.exceptions.Web3RPCError, match="insufficient fee"): send_transaction( w3, { "to": receiver, "value": 10000, + "gas": gas, "gasPrice": 1, }, )["transactionHash"] - assert "insufficient fee" in str(exc) - - # check all failed transactions are not included in blockchain - assert w3.eth.get_block_number() - initial_block_number <= diff # Deploy multiple contracts contracts = { @@ -592,3 +574,17 @@ def test_textual(mantra, connect_mantra, tmp_path): sign_mode="textual", ) assert rsp["code"] == 0, rsp["raw_log"] + + +@pytest.mark.connect +def test_connect_key_source(connect_mantra, tmp_path): + test_key_src(None, connect_mantra, tmp_path) + + +def test_key_src(mantra, connect_mantra, tmp_path): + cli = connect_mantra.cosmos_cli(tmp_path) + acct, mnemonic = Account.create_with_mnemonic(num_words=24) + src = tmp_path / "mnemonic.txt" + src.write_text(mnemonic) + addr = cli.create_account("user", source=src)["address"] + assert bech32_to_eth(addr) == acct.address diff --git a/integration_tests/test_call.py b/integration_tests/test_call.py index a31846c3..949c4dbb 100644 --- a/integration_tests/test_call.py +++ b/integration_tests/test_call.py @@ -6,7 +6,6 @@ from .utils import Greeter, build_and_deploy_contract_async -@pytest.mark.skip(reason="skipping temporary_contract_code test") def test_temporary_contract_code(mantra): state = 100 w3: Web3 = mantra.w3 @@ -34,7 +33,6 @@ def test_temporary_contract_code(mantra): assert (state,) == w3.codec.decode(("uint256",), result) -@pytest.mark.skip(reason="skipping override_state test") def test_override_state(mantra): w3: Web3 = mantra.w3 greeter = Greeter("Greeter") diff --git a/integration_tests/test_contract.py b/integration_tests/test_contract.py index c63bc79c..085c37a9 100644 --- a/integration_tests/test_contract.py +++ b/integration_tests/test_contract.py @@ -1,5 +1,6 @@ import asyncio import json +import time from pathlib import Path import pytest @@ -48,6 +49,7 @@ build_and_deploy_contract_async, build_contract, build_deploy_contract_async, + derive_new_account, w3_wait_for_new_blocks_async, ) @@ -315,14 +317,12 @@ async def test_4337(mantra, connect_mantra): ) -# TODO: rm flaky and enlarge num after evm mempool is ready -@pytest.mark.flaky(max_runs=5) async def test_deploy_multi(mantra): w3 = mantra.async_w3 name = "community" key = KEYS[name] owner = ADDRS[name] - num = 2 + num = 10 res = build_contract("ERC20MinterBurnerDecimals") args_list = [(w3, res, (f"MyToken{i}", f"MTK{i}", 18), key) for i in range(num)] tx_results = await asyncio.gather( @@ -344,6 +344,33 @@ async def test_deploy_multi(mantra): assert await ERC20.fns.balanceOf(owner).call(w3, to=token) == total +async def test_transfer_multi(geth): + w3 = geth.async_w3 + contract = await build_and_deploy_contract_async( + w3, "TestERC20A", key=KEYS["validator"] + ) + token = contract.address + owner = ACCOUNTS["validator"] + transfer_amt = 1 + base_gas_price = await w3.eth.gas_price + nonce_start = await w3.eth.get_transaction_count(owner.address) + tasks = [] + + receivers = [derive_new_account(4 + i).address for i in range(8)] + begin = time.time() + for i in range(10000): + receiver = receivers[i % 8] + nonce = nonce_start + i + gas_price = base_gas_price + tasks.append( + ERC20.fns.transfer(receiver, transfer_amt).transact( + w3, owner, to=token, nonce=nonce, gasPrice=gas_price + ) + ) + await asyncio.gather(*tasks) + print("total time", time.time() - begin) + + async def test_upgrade(mantra): w3 = mantra.async_w3 owner = ADDRS["community"] diff --git a/integration_tests/test_debug_traceblock.py b/integration_tests/test_debug_traceblock.py index 4d73b957..0aa04c2a 100644 --- a/integration_tests/test_debug_traceblock.py +++ b/integration_tests/test_debug_traceblock.py @@ -1,4 +1,6 @@ +import pytest import requests +import web3 from pystarport import ports from .utils import ( @@ -34,8 +36,14 @@ def test_traceblock(mantra): "nonce": nonce + n, } signed = sign_transaction(w3, tx, acc.key) - txhash = w3.eth.send_raw_transaction(signed.raw_transaction) - txhashes.append(txhash) + if n == total - 1: + with pytest.raises( + web3.exceptions.Web3RPCError, match="insufficient funds" + ): + w3.eth.send_raw_transaction(signed.raw_transaction) + else: + txhash = w3.eth.send_raw_transaction(signed.raw_transaction) + txhashes.append(txhash) for txhash in txhashes[0 : total - 1]: res = w3.eth.wait_for_transaction_receipt(txhash) assert res.status == 1 diff --git a/integration_tests/test_eip1559.py b/integration_tests/test_eip1559.py index f501a9ff..ce1b0563 100644 --- a/integration_tests/test_eip1559.py +++ b/integration_tests/test_eip1559.py @@ -27,6 +27,8 @@ async def test_dynamic_fee_tx(mantra, connect_mantra, update_params=True): before = await w3.eth.get_balance(ADDRS["community"]) tip_price = 10000000000 max_price = 1000000000000 + tip_price + begin = await w3.eth.block_number + await w3_wait_for_block_async(w3, begin + 1) tx = { "to": "0x0000000000000000000000000000000000000000", "value": amount, diff --git a/integration_tests/test_eip7702.py b/integration_tests/test_eip7702.py index 8cfa5e2a..c6bea338 100644 --- a/integration_tests/test_eip7702.py +++ b/integration_tests/test_eip7702.py @@ -57,11 +57,14 @@ async def test_eoa(cluster): data = {"to": sender, "value": 10000, "gasPrice": gas_price, "gas": gas} data["nonce"] = cluster.w3.eth.get_transaction_count(sender) + 1 + hash = send_transaction_sync(cluster.w3, data, key=key, check=False) + if isinstance(cluster, Geth): - send_transaction_sync(cluster.w3, data, key=key, check=False) + with pytest.raises(web3.exceptions.TransactionNotFound): + await cluster.w3.eth.get_transaction_receipt(hash) else: - with pytest.raises(web3.exceptions.Web3RPCError, match="invalid sequence"): - send_transaction_sync(cluster.w3, data, key=key, check=False) + with pytest.raises(web3.exceptions.RequestTimedOut): + await cluster.w3.eth.get_transaction_receipt(hash) # clear code clear_tx = dict(tx) @@ -83,5 +86,5 @@ async def test_eoa(cluster): assert reset_code.hex().startswith(""), "Code was not clear!" # TODO: https://github.com/cosmos/evm/issues/493 - # data = {"to": sender, "value": 10000, "gasPrice": gas_price, "gas": gas} - # send_transaction_sync(cluster.w3, data, key=key)["transactionHash"] + data = {"to": sender, "value": 10000, "gasPrice": gas_price, "gas": gas} + send_transaction_sync(cluster.w3, data, key=key)["transactionHash"] diff --git a/integration_tests/test_erc20_precompile.py b/integration_tests/test_erc20_precompile.py new file mode 100644 index 00000000..6774049c --- /dev/null +++ b/integration_tests/test_erc20_precompile.py @@ -0,0 +1,132 @@ +import enum +from pathlib import Path +from typing import Unpack + +import pytest +import web3 +from eth_abi import encode +from eth_contract.create2 import create2_deploy +from eth_contract.deploy_utils import ( + ensure_create2_deployed, + ensure_multicall3_deployed, +) +from eth_contract.erc20 import ERC20 +from eth_contract.multicall3 import MULTICALL3, Call3, multicall +from eth_contract.utils import send_transaction +from eth_utils import to_bytes, to_checksum_address + +from .utils import ACCOUNTS, ADDRS + +GAS_PRICE = 1000000000000 +WOM = to_checksum_address("0x4200000000000000000000000000000000000006") +ERC20Bin = bytes.fromhex( + Path(__file__).parent.joinpath("configs/ERC20.bin").read_text() +) +ERC20Salt = bytes.fromhex( + "636dd1d57837e7dce61901468217da9975548dcb3ecc24d84567feb93cd11e36" +) + + +class BankMethod(enum.IntEnum): + NAME = 0 + SYMBOL = 1 + DECIMALS = 2 + TOTAL_SUPPLY = 3 + BALANCE_OF = 4 + TRANSFER_FROM = 5 + + def args(self, *args: Unpack[bytes]) -> bytes: + return b"".join([bytes([self.value]), *args]) + + +@pytest.mark.asyncio +async def test_bank_precompile(mantra): + w3 = mantra.async_w3 + acct = ACCOUNTS["community"] + validator = ACCOUNTS["validator"] + await ensure_multicall3_deployed(w3, validator, gasPrice=GAS_PRICE) + + bank = to_checksum_address("0x0000000000000000000000000000000000000807") + user = acct.address + denom = "atoken" + calls = [ + Call3(bank, data=BankMethod.NAME.args(denom.encode())), + Call3(bank, data=BankMethod.SYMBOL.args(denom.encode())), + Call3(bank, data=BankMethod.DECIMALS.args(denom.encode())), + Call3(bank, data=BankMethod.TOTAL_SUPPLY.args(denom.encode())), + Call3( + bank, data=BankMethod.BALANCE_OF.args(to_bytes(hexstr=user), denom.encode()) + ), + ] + results = await MULTICALL3.fns.aggregate3(calls).call(w3) + expected = ( + (True, b"Test Coin"), + (True, b"ATOKEN"), + (True, bytes([18])), + (True, (1000000000000).to_bytes(32, "big")), + (True, (1000000000000).to_bytes(32, "big")), + ) + assert expected == results + + # owner can transfer funds on bank precompile directly + recipient = ADDRS["validator"] + amount = 1000 + data = BankMethod.TRANSFER_FROM.args( + to_bytes(hexstr=user), + to_bytes(hexstr=recipient), + amount.to_bytes(32, "big"), + denom.encode(), + ) + await send_transaction(w3, acct, to=bank, data=data, gasPrice=GAS_PRICE) + + with pytest.raises(web3.exceptions.ContractLogicError): + # wrong user fail + await send_transaction(w3, validator, to=bank, data=data, gasPrice=GAS_PRICE) + + +@pytest.mark.asyncio +async def test_bank_erc20(mantra): + w3 = mantra.async_w3 + acct = ACCOUNTS["community"] + validator = ACCOUNTS["validator"] + await ensure_create2_deployed(w3, validator, gasPrice=GAS_PRICE) + await ensure_multicall3_deployed(w3, validator, gasPrice=GAS_PRICE) + + bank = to_checksum_address("0x0000000000000000000000000000000000000807") + user = acct.address + denom = "atoken" + + initcode = ERC20Bin + encode(["string", "address"], [denom, bank]) + token = await create2_deploy(w3, validator, initcode, ERC20Salt, gasPrice=GAS_PRICE) + + test_user = to_checksum_address(b"\x01" * 20) + await ERC20.fns.transfer(test_user, 1).transact( + w3, acct, to=token, gasPrice=GAS_PRICE + ) + + expected = ["Test Coin", "ATOKEN", 18, 1000000000000, 1] + calls = [ + (token, ERC20.fns.name()), + (token, ERC20.fns.symbol()), + (token, ERC20.fns.decimals()), + (token, ERC20.fns.totalSupply()), + (token, ERC20.fns.balanceOf(test_user)), + ] + result = await multicall(w3, calls) + assert expected == result + + # owner can transfer funds on bank precompile directly + recipient = validator.address + amount = 1000 + before = ( + await ERC20.fns.balanceOf(user).call(w3, to=token), + await ERC20.fns.balanceOf(recipient).call(w3, to=token), + ) + await ERC20.fns.transfer(recipient, amount).transact( + w3, acct, to=token, gasPrice=GAS_PRICE + ) + after = ( + await ERC20.fns.balanceOf(user).call(w3, to=token), + await ERC20.fns.balanceOf(recipient).call(w3, to=token), + ) + assert after == (before[0] - amount, before[1] + amount) diff --git a/integration_tests/test_filters.py b/integration_tests/test_filters.py index e67f9626..ffc6fa18 100644 --- a/integration_tests/test_filters.py +++ b/integration_tests/test_filters.py @@ -18,7 +18,7 @@ async def test_get_logs_by_topic(mantra): contract = await build_and_deploy_contract_async(w3, "Greeter") topic = f"0x{Web3.keccak(text='ChangeGreeting(address,string)').hex()}" tx = await contract.functions.setGreeting("world").build_transaction() - await send_transaction(w3, ACCOUNTS["community"], **tx) + res = await send_transaction(w3, ACCOUNTS["community"], **tx) current = await w3.eth.block_number # invalid block ranges @@ -44,7 +44,14 @@ async def test_get_logs_by_topic(mantra): # log exists logs = await w3.eth.get_logs({"topics": [topic]}) assert len(logs) == 1 - assert logs[0]["address"] == contract.address + log = logs[0] + assert all( + log[key] == res[key] + for key in ["transactionHash", "transactionIndex", "blockNumber", "blockHash"] + ) + assert log["address"] == contract.address + assert log["blockTimestamp"] == res["logs"][0]["blockTimestamp"] + assert log["blockTimestamp"] != "0x0" # Wait and confirm log doesn't appear in new blocks await w3_wait_for_new_blocks_async(w3, 2) diff --git a/integration_tests/test_max_gas_wanted.py b/integration_tests/test_max_gas_wanted.py new file mode 100644 index 00000000..61d1cdf3 --- /dev/null +++ b/integration_tests/test_max_gas_wanted.py @@ -0,0 +1,84 @@ +from pathlib import Path + +import pytest +from pystarport import ports + +from .network import setup_custom_mantra +from .utils import ( + ADDRS, + KEYS, + modify_command_in_supervisor_config, + send_txs, + wait_for_new_blocks, + wait_for_port, +) + +origin_cmd = None + + +@pytest.fixture(scope="module") +def custom_mantra(request, tmp_path_factory): + chain = request.config.getoption("chain_config") + path = tmp_path_factory.mktemp("default") + yield from setup_custom_mantra( + path, + 27100, + Path(__file__).parent / "configs/default.jsonnet", + chain=chain, + ) + + +@pytest.mark.unmarked +@pytest.mark.parametrize("max_gas_wanted", [80000000, 40000000, 25000000, 500000, None]) +def test_tx_inclusion(custom_mantra, max_gas_wanted): + """ + - send multiple heavy transactions at the same time. + - check they are included in consecutively blocks without failure. + + test against different max-gas-wanted configuration. + """ + + def fn(cmd): + global origin_cmd + if origin_cmd is None: + origin_cmd = cmd + if max_gas_wanted is None: + return origin_cmd + return f"{origin_cmd} --evm.max-tx-gas-wanted {max_gas_wanted}" + + modify_command_in_supervisor_config( + custom_mantra.base_dir / "tasks.ini", + lambda cmd: fn(cmd), + custom_mantra.chain_binary, + ) + custom_mantra.supervisorctl("update") + wait_for_port(ports.evmrpc_port(custom_mantra.base_port(0))) + cli = custom_mantra.cosmos_cli() + wait_for_new_blocks(cli, 2) + + # reset to origin_cmd only + if max_gas_wanted is None: + return + + w3 = custom_mantra.w3 + block_gas_limit = 81500000 + tx_gas_limit = 80000000 + max_tx_in_block = block_gas_limit // min(max_gas_wanted, tx_gas_limit) + print("max_tx_in_block", max_tx_in_block) + to = ADDRS["validator"] + params = {"gas": tx_gas_limit} + _, sended_hash_set = send_txs(w3, cli, to, list(KEYS.values())[0:4], params) + block_nums = [ + w3.eth.wait_for_transaction_receipt(h).blockNumber for h in sended_hash_set + ] + block_nums.sort() + print(f"all block numbers: {block_nums}") + # the transactions should be included according to max_gas_wanted + if max_tx_in_block == 1: + for block_num, next_block_num in zip(block_nums, block_nums[1:]): + assert next_block_num == block_num + 1 or next_block_num == block_num + 2 + else: + for num in block_nums[1:max_tx_in_block]: + assert num == block_nums[0] + for num in block_nums[max_tx_in_block:]: + assert num == block_nums[0] + 1 or num == block_nums[0] + 2 diff --git a/integration_tests/test_mempool.py b/integration_tests/test_mempool.py index 964594e3..344e31a2 100644 --- a/integration_tests/test_mempool.py +++ b/integration_tests/test_mempool.py @@ -8,8 +8,10 @@ ADDRS, KEYS, Greeter, + module_address, send_txs, sign_transaction, + submit_gov_proposal, wait_for_new_blocks, ) @@ -79,7 +81,7 @@ def test_mempool(mantra_mempool): @pytest.mark.flaky(max_runs=3) -def test_mempool_nonce(mantra_mempool): +def test_mempool_nonce(mantra_mempool, tmp_path): """ test the nonce logic in check-tx after new block is created. @@ -94,13 +96,31 @@ def test_mempool_nonce(mantra_mempool): transactions with local nonce. """ w3: Web3 = mantra_mempool.w3 - cli = mantra_mempool.cosmos_cli(0) - wait_for_new_blocks(cli, 1, sleep=0.1) + cli = mantra_mempool.cosmos_cli() + p = cli.get_params("consensus")["params"] + # adjust to 128KB txMaxSize to avoid oversized data when broadcast + max_bytes = 131072 + p["block"]["max_bytes"] = max_bytes + p["evidence"]["max_bytes"] = max_bytes + p.pop("version", None) + submit_gov_proposal( + mantra_mempool, + tmp_path, + messages=[ + { + "@type": "/cosmos.consensus.v1.MsgUpdateParams", + "authority": module_address("gov"), + **p, + } + ], + ) + p = cli.get_params("consensus")["params"] + assert int(p["block"]["max_bytes"]) == max_bytes sender = ADDRS["community"] orig_nonce = w3.eth.get_transaction_count(sender) height = w3.eth.get_block_number() local_nonce = orig_nonce - tx_bytes = 1000000 # can only include one tx at a time + tx_bytes = max_bytes // 2 + 1 # can only include one tx at a time def send_with_nonce(nonce): tx = { diff --git a/integration_tests/test_precompile.py b/integration_tests/test_precompile.py index de069ae2..1d08e161 100644 --- a/integration_tests/test_precompile.py +++ b/integration_tests/test_precompile.py @@ -5,6 +5,7 @@ from eth_account import Account from eth_account.messages import encode_defunct from eth_utils import to_hex +from hexbytes import HexBytes from py_ecc.bls12_381 import G1, G2, add, multiply from py_ecc.fields import FQ @@ -612,3 +613,42 @@ async def test_bls12381_map_g2(mantra, field_pair): } ) assert len(res) == 256, f"G2 map result should be 256 bytes, got {len(res)}" + + +async def test_move_precompile_to(mantra): + w3 = mantra.async_w3 + moved = w3.to_checksum_address("0x00000000000000000000000000000000deadbeef") + input = 1 + code = "0x60003560010160005260206000f3" # bytecode: adds one to input + res = await w3.eth.call( + { + "to": PRECOMPILE_ECRECOVER, + "data": input.to_bytes(32, byteorder="big"), + }, + "latest", + { + PRECOMPILE_ECRECOVER: { + "code": code, + "movePrecompileToAddress": moved, + }, + }, + ) + output = 2 + assert res == output.to_bytes(32, byteorder="big") + + input = "0x82f3df49d3645876de6313df2bbe9fbce593f21341a7b03acdb9423bc171fcc9000000000000000000000000000000000000000000000000000000000000001cba13918f50da910f2d55a7ea64cf716ba31dad91856f45908dde900530377d8a112d60f36900d18eb8f9d3b4f85a697b545085614509e3520e4b762e35d0d6bd" # noqa: E501 + res = await w3.eth.call( + { + "to": moved, + "data": HexBytes(input), + }, + "latest", + { + PRECOMPILE_ECRECOVER: { + "code": code, + "movePrecompileToAddress": moved, + }, + }, + ) + output = "0x000000000000000000000000c6e93f4c1920eaeaa1e699f76a7a8c18e3056074" + assert res == HexBytes(output) diff --git a/integration_tests/test_upgrade.py b/integration_tests/test_upgrade.py index 2b558014..ee219883 100644 --- a/integration_tests/test_upgrade.py +++ b/integration_tests/test_upgrade.py @@ -235,7 +235,7 @@ def exec(c, tmp_path): "mantra-canary-net-1-node1", "mantra-canary-net-1-node2", ) - wait_for_new_blocks(cli, 1) + wait_for_new_blocks(cli, 2) check_basic_eth_tx(c.w3, contract, acc_b, addr_a, "world rc8") height = cli.block_height() diff --git a/integration_tests/utils.py b/integration_tests/utils.py index da149c7d..c4447554 100644 --- a/integration_tests/utils.py +++ b/integration_tests/utils.py @@ -35,6 +35,7 @@ from eth_contract.weth import WETH, WETH9_ARTIFACT from eth_utils import to_checksum_address from hexbytes import HexBytes +from pystarport import ports from web3 import AsyncWeb3 from web3._utils.transactions import fill_nonce, fill_transaction_defaults @@ -955,6 +956,9 @@ def assert_register_erc20_denom(c, addr, tmp_path): erc20_denom = f"erc20:{addr}" res = c.cosmos_cli().query_erc20_token_pair(erc20_denom) assert res["erc20_address"] == addr, res + p = ports.api_port(c.base_port(0)) + url = f"http://127.0.0.1:{p}/cosmos/evm/erc20/v1/token_pairs/{erc20_denom}" + assert requests.get(url).json()["token_pair"] == res async def assert_weth_flow(w3, weth_addr, owner, account): @@ -1014,3 +1018,17 @@ async def assert_tf_flow(w3, receiver, signer1, signer2, tf_erc20_addr): receiver_balance = await ERC20.fns.balanceOf(receiver).call(w3, to=tf_erc20_addr) assert receiver_balance == receiver_balance_bf + approve_amt receiver_balance_bf = receiver_balance + + +def modify_command_in_supervisor_config(ini: Path, fn, chain_binary, **kwargs): + "replace the first node with the instrumented binary" + pattern = rf"^command = ({re.escape(chain_binary)} .*$)" + ini.write_text( + re.sub( + pattern, + lambda m: f"command = {fn(m.group(1))}", + ini.read_text(), + flags=re.M, + **kwargs, + ) + ) diff --git a/nix/mantrachain/default.nix b/nix/mantrachain/default.nix index 08a06d9e..20205333 100644 --- a/nix/mantrachain/default.nix +++ b/nix/mantrachain/default.nix @@ -1,7 +1,7 @@ { lib, stdenv, - buildGo123Module, + buildGo124Module, fetchFromGitHub, rev ? "dirty", nativeByteOrder ? true, # nativeByteOrder mode will panic on big endian machines @@ -14,9 +14,9 @@ let wasmvmVersion = "v3.0.0"; # Use static packages for Linux to ensure musl compatibility - buildPackages = if stdenv.isLinux then pkgsStatic else { inherit stdenv buildGo123Module; }; + buildPackages = if stdenv.isLinux then pkgsStatic else { inherit stdenv buildGo124Module; }; buildStdenv = buildPackages.stdenv; - buildGo123Module' = if stdenv.isLinux then buildPackages.buildGo123Module else buildGo123Module; + buildGo124Module' = if stdenv.isLinux then buildPackages.buildGo124Module else buildGo124Module; # Download wasmvm libraries as fixed-output derivations wasmvmLibs = { @@ -73,7 +73,7 @@ let ]; in -buildGo123Module' rec { +buildGo124Module' rec { inherit pname version @@ -82,12 +82,12 @@ buildGo123Module' rec { ; stdenv = buildStdenv; src = fetchFromGitHub { - owner = "MANTRA-Chain"; + owner = "mmsqe"; repo = pname; - rev = "cef42e79e72cabea6cb0b191222c982a8171d9da"; - hash = "sha256-hhgQ2/rt5JouHj83c16lrdea1pDC64JQoTBmjw+4OdY="; + rev = "b5d29b626a1b047bee15f085b9cbba9c441ebe95"; + hash = "sha256-pirlAjUi3OvbJVOtnz6AL5Gn0EjG3C5MHdVM+phqai8="; }; - vendorHash = "sha256-g+0SmUomCtZ/95I5qKgVtptjgWJL/v6v2zx1ksIok3A="; + vendorHash = "sha256-+s2qjLdAUEFEPEeAESIEdQLuIx1qlP4qqbFE4ap2acA="; proxyVendor = true; subPackages = [ "cmd/mantrachaind" ]; CGO_ENABLED = "1"; diff --git a/nix/sources.json b/nix/sources.json index 784406cb..5d7c4206 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -29,10 +29,10 @@ "homepage": "", "owner": "NixOS", "repo": "nixpkgs", - "rev": "daa477e013dd736c939fcc111dc4dd889bc17c94", - "sha256": "1vbbl1iy5wxmwww8hi1ngcby6mnyzn7l5x9yxf58yd9z0nmx6p74", + "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", + "sha256": "1s2gr5rcyqvpr58vxdcb095mdhblij9bfzaximrva2243aal3dgx", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/daa477e013dd736c939fcc111dc4dd889bc17c94.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/50ab793786d9de88ee30ec4e4c24fb4236fc2674.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "poetry2nix": {