ethereum / populus

The Ethereum development framework with the most cute animal pictures
http://populus.readthedocs.org/
321 stars 321 forks source link

multi-contract deployments #420

Closed Netherdrake closed 6 years ago

Netherdrake commented 6 years ago

I am trying to deploy contracts that link to (use) previously deployed contracts.

To make things simple, I'm basing this issue on vanilla Greeter contract as produced by populus init in 2.2.0.

Here is a much simplified deployment script to present the issue I'm running into.

from populus import Project
from populus.chain.base import BaseChain
# from populus.utils.wait import wait_for_transaction_receipt
# from web3 import Web3

def load_contract(chain: BaseChain, contract_name, address):
    contract_factory = chain.provider.get_contract_factory(contract_name)
    return contract_factory(address=address)

def load_contract2(chain: BaseChain, contract_name, address):
    return chain.provider.get_contract(contract_name)

def deploy_contract(chain: BaseChain, owner, contract_name, args=[]):
    contract, _ = chain.provider.get_or_deploy_contract(
        contract_name,
        deploy_transaction={'from': owner},
        deploy_args=args,
    )
    return contract

contract_name = 'Greeter'
chain_name = 'testrpc'

def deploy():
    with Project().get_chain(chain_name) as chain:
        print(f"Head block is {chain.web3.eth.blockNumber} on the {chain_name} chain")

        owner = chain.web3.eth.coinbase
        print('Owner address is', owner)

        print(f'Deploying {contract_name}.sol')
        instance = deploy_contract(chain, owner, contract_name)
        print(f'{contract_name} address is', instance.address)

        # instance = load_contract2(chain, contract_name, instance.address)
        instance = load_contract(chain, contract_name, instance.address)

        print(instance.call().greet())

        return instance.address

def load(contract_address):
    with Project().get_chain(chain_name) as chain:
        print(f"Head block is {chain.web3.eth.blockNumber} on the {chain_name} chain")
        instance = load_contract(chain, contract_name, contract_address)
        print(f'{contract_name} address is', instance.address)
        print(instance.call().greet())

if __name__ == '__main__':
    addr = deploy()
    load(addr)

The above code will fail. For some reason, the Greeter contract instance in a load()'s with Project().get_chain(chain_name) as chain: scope will be unusable, and throw errors such as:

web3.exceptions.BadFunctionCallOutput: Could not transact with/call contract function, is contract deployed correctly and chain synced?

In the case of calling into another (already deployed contract), the transaction will fail. I tried inspecting instances from different scopes by comparing the address/abi/code/bytecode and they seem to be the same, so I don't see an obvious reason for why this fails.

Perhaps the testrpc/tester state is ephemeral, and bound to individual Project.get_chain scope?

I wanted to try with a Ropsten testnet, but unfortunately I'm having some trouble with that as well.

monkey

veox commented 6 years ago

Perhaps the testrpc/tester state is ephemeral, and bound to individual Project.get_chain scope?

Basically, yes.

veox commented 6 years ago

You could do the

with Project().get_chain(chain_name) as chain:

block in your __name__ == '__main__' block, and pass chain around.

Netherdrake commented 6 years ago

Thanks for the heads up.

In my real-world case, these deployments are run at separate times (over span of months), so there is absolutely no way to share a chain instance there.

I suppose I could refactor my codebase to share the chain for testrpc and tester, and do the partial application for testnet and mainnet.