diff --git a/proxy/plugin/solana_rest_api_tools.py b/proxy/plugin/solana_rest_api_tools.py index cca640fa4..abd894222 100644 --- a/proxy/plugin/solana_rest_api_tools.py +++ b/proxy/plugin/solana_rest_api_tools.py @@ -760,6 +760,7 @@ def create_account_list_by_emulate(signer, client, ethTrx): sender_ether = bytes.fromhex(ethTrx.sender()) add_keys_05 = [] trx = Transaction() + new_neon_token_acccounts = [] output_json = call_emulated(ethTrx.toAddress.hex(), sender_ether.hex(), ethTrx.callData.hex(), hex(ethTrx.value)) logger.debug("emulator returns: %s", json.dumps(output_json, indent=3)) @@ -789,7 +790,11 @@ def create_account_list_by_emulate(signer, client, ethTrx): code_account_balance = client.get_minimum_balance_for_rent_exemption(code_account_size)["result"] trx.add(createAccountWithSeedTrx(signer.public_key(), signer.public_key(), seed, code_account_balance, code_account_size, PublicKey(evm_loader_id))) add_keys_05.append(AccountMeta(pubkey=code_account, is_signer=False, is_writable=acc_desc["writable"])) - trx.add(createEtherAccountTrx(client, address, evm_loader_id, signer, code_account)[0]) + + (create_trx, solana_address, token_address) = createEtherAccountTrx(client, address, evm_loader_id, signer, code_account) + trx.add(create_trx) + new_neon_token_acccounts.append(token_address) + if address == sender_ether and NEW_USER_AIRDROP_AMOUNT > 0: trx.add(transfer2(Transfer2Params( amount=NEW_USER_AIRDROP_AMOUNT*1_000_000_000, @@ -806,13 +811,13 @@ def create_account_list_by_emulate(signer, client, ethTrx): str(NEW_USER_AIRDROP_AMOUNT)) for token_account in output_json["token_accounts"]: - add_keys_05.append(AccountMeta(pubkey=token_account["key"], is_signer=False, is_writable=True)) + add_keys_05.append(AccountMeta(pubkey=PublicKey(token_account["key"]), is_signer=False, is_writable=True)) - if token_account["new"]: - trx.add(create_associated_token_account(signer.public_key(), token_account["owner"], token_account["mint"])) + if token_account["new"] and (PublicKey(token_account["key"]) not in new_neon_token_acccounts): + trx.add(create_associated_token_account(signer.public_key(), PublicKey(token_account["owner"]), PublicKey(token_account["mint"]))) for account_meta in output_json["solana_accounts"]: - add_keys_05.append(AccountMeta(pubkey=account_meta["pubkey"], is_signer=account_meta["is_signer"], is_writable=account_meta["is_writable"])) + add_keys_05.append(AccountMeta(pubkey=PublicKey(account_meta["pubkey"]), is_signer=account_meta["is_signer"], is_writable=account_meta["is_writable"])) caller_token = get_associated_token_address(PublicKey(sender_sol), ETH_TOKEN_MINT_ID) @@ -969,7 +974,7 @@ def createEtherAccountTrx(client, ether, evm_loader_id, signer, code_acc=None): AccountMeta(pubkey=ASSOCIATED_TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), AccountMeta(pubkey=rentid, is_signer=False, is_writable=False), ])) - return (trx, sol) + return (trx, sol, associated_token) def write_trx_to_holder_account(signer, client, holder, ethTrx): diff --git a/proxy/test_erc20_wrapper_contract.py b/proxy/test_erc20_wrapper_contract.py new file mode 100644 index 000000000..90d9c36a8 --- /dev/null +++ b/proxy/test_erc20_wrapper_contract.py @@ -0,0 +1,166 @@ +## File: test_erc20_wrapper_contract.py +## Integration test for the Neon ERC20 Wrapper contract. + +import unittest +import os +from web3 import Web3 +from solcx import install_solc + +# install_solc(version='latest') +install_solc(version='0.7.6') +from solcx import compile_source + +EXTRA_GAS = int(os.environ.get("EXTRA_GAS", "100000")) +proxy_url = os.environ.get('PROXY_URL', 'http://localhost:9090/solana') +proxy = Web3(Web3.HTTPProvider(proxy_url)) +admin = proxy.eth.account.create('issues/neonlabsorg/proxy-model.py/197/admin') +user = proxy.eth.account.create('issues/neonlabsorg/proxy-model.py/197/user') +proxy.eth.default_account = admin.address + +NAME = 'NEON' +SYMBOL = 'NEO' + +# token_mint::id = "HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU" in Base58 +# Convert Base58 to hex number: +TOKEN_MINT = bytes.fromhex('f396da383e57418540f8caa598584f49a3b50d256f75cb6d94d101681d6d9d21') + +# Standard interface of ERC20 contract to generate ABI for wrapper +ERC20_INTERFACE_SOURCE = ''' +pragma solidity >=0.7.0; + +interface IERC20 { + function decimals() external view returns (uint8); + function totalSupply() external view returns (uint256); + function balanceOf(address who) external view returns (uint256); + function allowance(address owner, address spender) external view returns (uint256); + function transfer(address to, uint256 value) external returns (bool); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); +} +''' + +# Copy of contract: https://github.com/neonlabsorg/neon-evm/blob/develop/evm_loader/SPL_ERC20_Wrapper.sol +ERC20_WRAPPER_SOURCE = ''' +pragma solidity >=0.7.0; + +interface IERC20 { + function decimals() external view returns (uint8); + function totalSupply() external view returns (uint256); + function balanceOf(address who) external view returns (uint256); + function allowance(address owner, address spender) external view returns (uint256); + function transfer(address to, uint256 value) external returns (bool); + function approve(address spender, uint256 value) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + function approveSolana(bytes32 spender, uint64 value) external returns (bool); + event ApprovalSolana(address indexed owner, bytes32 indexed spender, uint64 value); +} + +/*abstract*/ contract NeonERC20Wrapper /*is IERC20*/ { + address constant NeonERC20 = 0xff00000000000000000000000000000000000001; + + string public name; + string public symbol; + bytes32 public tokenMint; + + constructor( + string memory _name, + string memory _symbol, + bytes32 _tokenMint + ) { + name = _name; + symbol = _symbol; + tokenMint = _tokenMint; + } + + fallback() external { + bytes memory call_data = abi.encodePacked(tokenMint, msg.data); + (bool success, bytes memory result) = NeonERC20.delegatecall(call_data); + + require(success, string(result)); + + assembly { + return(add(result, 0x20), mload(result)) + } + } +} +''' + +class Test_erc20_wrapper_contract(unittest.TestCase): + @classmethod + def setUpClass(cls): + print("\n\nhttps://github.com/neonlabsorg/proxy-model.py/issues/197") + print('admin.key:', admin.key.hex()) + print('admin.address:', admin.address) + print('user.key:', user.key.hex()) + print('user.address:', user.address) + cls.deploy_erc20_wrapper_contract(cls) + + def deploy_erc20_wrapper_contract(self): + compiled_interface = compile_source(ERC20_INTERFACE_SOURCE) + interface_id, interface = compiled_interface.popitem() + self.interface = interface + + compiled_wrapper = compile_source(ERC20_WRAPPER_SOURCE) + wrapper_id, wrapper_interface = compiled_wrapper.popitem() + self.wrapper = wrapper_interface + + erc20 = proxy.eth.contract(abi=self.wrapper['abi'], bytecode=wrapper_interface['bin']) + nonce = proxy.eth.get_transaction_count(proxy.eth.default_account) + tx = {'nonce': nonce} + tx_constructor = erc20.constructor(NAME, SYMBOL, TOKEN_MINT).buildTransaction(tx) + tx_deploy = proxy.eth.account.sign_transaction(tx_constructor, admin.key) + #print('tx_deploy:', tx_deploy) + tx_deploy_hash = proxy.eth.send_raw_transaction(tx_deploy.rawTransaction) + print('tx_deploy_hash:', tx_deploy_hash.hex()) + tx_deploy_receipt = proxy.eth.wait_for_transaction_receipt(tx_deploy_hash) + print('tx_deploy_receipt:', tx_deploy_receipt) + print('deploy status:', tx_deploy_receipt.status) + self.contract_address= tx_deploy_receipt.contractAddress + + def test_erc20_name(self): + erc20 = proxy.eth.contract(address=self.contract_address, abi=self.wrapper['abi']) + name = erc20.functions.name().call() + self.assertEqual(name, NAME) + + def test_erc20_symbol(self): + erc20 = proxy.eth.contract(address=self.contract_address, abi=self.wrapper['abi']) + sym = erc20.functions.symbol().call() + self.assertEqual(sym, SYMBOL) + + def test_erc20_decimals(self): + erc20 = proxy.eth.contract(address=self.contract_address, abi=self.interface['abi']) + decs = erc20.functions.decimals().call() + self.assertEqual(decs, 9) + + def test_erc20_totalSupply(self): + erc20 = proxy.eth.contract(address=self.contract_address, abi=self.interface['abi']) + ts = erc20.functions.totalSupply().call() + self.assertGreater(ts, 0) + + def test_erc20_balanceOf(self): + erc20 = proxy.eth.contract(address=self.contract_address, abi=self.interface['abi']) + b = erc20.functions.balanceOf(admin.address).call() + self.assertGreater(b, 0) + b = erc20.functions.balanceOf(user.address).call() + self.assertEqual(b, 0) + + def test_erc20_transfer(self): + erc20 = proxy.eth.contract(address=self.contract_address, abi=self.interface['abi']) + nonce = proxy.eth.get_transaction_count(proxy.eth.default_account) + tx = {'nonce': nonce} + tx = erc20.functions.transfer(user.address, 1000).buildTransaction(tx) + tx = proxy.eth.account.sign_transaction(tx, admin.key) + tx_hash = proxy.eth.send_raw_transaction(tx.rawTransaction) + print('tx_hash:',tx_hash) + tx_receipt = proxy.eth.wait_for_transaction_receipt(tx_hash) + self.assertIsNotNone(tx_receipt) + +if __name__ == '__main__': + unittest.main() diff --git a/requirements.txt b/requirements.txt index 34f20ed7e..c3c2110a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,6 @@ ecdsa==0.16.0 pysha3==1.0.2 eth-keys==0.3.3 rlp==2.0.1 -web3 +web3==5.22.0 solana==0.10.0 +py-solc-x==1.1.0 \ No newline at end of file