Skip to content
Merged
17 changes: 11 additions & 6 deletions proxy/plugin/solana_rest_api_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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,
Expand All @@ -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)

Expand Down Expand Up @@ -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):
Expand Down
166 changes: 166 additions & 0 deletions proxy/test_erc20_wrapper_contract.py
Original file line number Diff line number Diff line change
@@ -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()
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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