Contracts
You can interact with contracts pythonically using ape! First, we need to obtain a contract instance. One way to do this is to deploy a contract. The other way is to initialize an already-deployed contract using its address.
From Deploy
Deploy contracts from your project using the project
root-level object.
The names of your contracts are properties on the project
object (e.g. project.MyContract
) and their types are ContractContainer.
NOTE: To avoid naming collisions with other properties on the project
object, you can also use the get_contract() method to retrieve contract containers.
When you deploy contracts, you get back a ContractInstance
:
from ape import accounts, project
dev = accounts.load("dev")
contract = project.MyContract.deploy(sender=dev)
You can alternatively use this syntax instead:
from ape import accounts, project
dev = accounts.load("dev")
contract = dev.deploy(project.MyContract)
If your contract requires constructor arguments then you will need to pass them to the contract in the args when deploying like this:
from ape import accounts, project
dev = accounts.load("dev")
contract = project.MyContract.deploy("argument1", "argument2", sender=dev)
you can alternatively use this syntax instead:
from ape import accounts, project
dev = accounts.load("dev")
contract = accounts.dev.deploy(project.MyContract, "argument1", "argument2")
With this technique, you can feed as many constructor arguments as your contract constructor requires.
NOTE: You can also publish the contract source code to an explorer upon deployment using the publish=
kwarg on the deploy methods.
More information on publishing contracts can be found in this guide.
If you do not pass the correct amount of constructor arguments when deploying, you will get an error showing your arguments don’t match what is in the ABI:
ArgumentsLengthError: The number of the given arguments (0) do not match what is defined in the ABI (2).
In this case it is saying that you have fed 0 constructor arguments but the contract requires 2 constructor arguments to deploy.
To show the arguments that your constructor requires you can use the .constructor method to see the constructor arguments that your contract requires:
In [0]: project.MyContract.constructor
Out[0]: constructor(string argument1, string argument2)
From Project Contract Address
You can also use the at() method from the same top-level project manager when you know the address of an already-deployed contract:
from ape import project
contract = project.MyContract.at("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")
From Any Address
If you already know the address of a contract, you can create instances of it using the Contract
top-level factory:
from ape import Contract
contract = Contract("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")
It will fetch the contract-type
using the explorer plugin from the active network, such as ape-etherscan.
If you have the ENS plugin installed, you can use .eth
domain names as the argument:
from ape import Contract
contract = Contract("v2.registry.ychad.eth")
From ABIs
You can load contracts using their ABIs:
from ape import Contract
address = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"
# Using a JSON str:
contract = Contract(
address, abi='[{"name":"foo","type":"fallback", "stateMutability":"nonpayable"}]'
)
# Using a JSON file path:
contract = Contract(address, abi="abi.json")
# Using a Python dictionary from JSON:
contract = Contract(
address,
abi=[{"name":"foo","type":"fallback", "stateMutability":"nonpayable"}]
)
This will create the Contract instance from the given ABI.
From Previous Deployment
Ape keeps track of your deployments for you so you can always refer back to a version that you deployed previously. On live networks, this history of deployments is saved; on local networks, this history lasts for the duration of your script.
Let’s say you previously deployed a smart contract called MyContract
on the rinkeby test network.
You could then refer back to it like so:
from ape import project, chain, accounts
def main():
account = accounts.test_accounts[0]
my_contract = chain.contracts.get_deployments(project.MyContract)[-1]
or
from ape import project, accounts
def main():
account = accounts.test_accounts[0]
my_contract = project.MyContract.deployments[-1]
my_contract
will be of type ContractInstance
.
get_deployments
returns a list of deployments you made of that contract type.
Contract Interaction
Then, after you have a contract instance, you can call methods on the contract. For example, let’s say you have a Vyper contract containing some functions:
@pure
@external
def get_static_list() -> DynArray[uint256, 3]:
return [1, 2, 3]
@external
def set_number(num: uint256):
assert msg.sender == self.owner, "!authorized"
self.prevNumber = self.myNumber
self.myNumber = num
You can call those functions by doing:
assert contract.get_static_list() == [1, 2, 3]
# Mutable calls are transactions and require a sender
receipt = contract.set_number(sender=dev)
Default, Fallback, and Direct Calls
To directly call an address, such as invoking a contract’s fallback
or receive
method, call a contract instance directly:
from ape import Contract, accounts
sender = accounts.load("dev")
contract = Contract("0x123...")
# Call the contract's fallback method.
receipt = contract(sender=sender, gas=40000, data="0x123")
Private Transactions
If you are using a provider that allows private mempool transactions, you are able to use the private=True
kwarg to publish your transaction into a private mempool.
For example, EVM providers likely will use the eth_sendPrivateTransaction
RPC to achieve this.
To send a private transaction, do the following:
receipt = contract.set_number(sender=dev, private=True)
The private=True
is available on all contract interactions.
Decoding and Encoding Inputs
If you want to separately decode and encode inputs without sending a transaction or making a call, you can achieve this with Ape.
If you know the method you want to use when decoding or encoding, you can call methods encode_input()
or decode_input()
on the method handler from a contract:
from ape import Contract
# HexBytes(0x3fb5c1cb00000000000000000000000000000000000000000000000000000000000000de)
contract = Contract("0x...")
bytes_value = contract.my_method.encode_input(0, 1, 2)
In the example above, the bytes value returned contains the method ID selector prefix 3fb5c1c
.
Alternatively, you can decode input:
from ethpm_types import HexBytes
from ape import Contract
contract = Contract("0x...")
selector_str, input_dict = contract.my_method.decode_input(HexBytes("0x123..."))
In the example above, selector_str
is the string version of the method ID, e.g. my_method(unit256,uint256)
.
The input dict is a mapping of input names to their decoded values, e.g {"foo": 2, "owner": "0x123..."}
.
If an input does not have a name, its key is its stringified input index.
If you don’t know the method’s ABI and you have calldata, you can use a ContractInstance
or ContractContainer
directly:
import ape
# Fetch a contract
contract = ape.Contract("0x...")
# Alternative, use a contract container from ape.project
# contract = ape.project.MyContract
# Only works if unique amount of args.
bytes_value = contract.encode_input(0, 1, 2, 4, 5)
method_id, input_dict = contract.decode_input(bytes_value)
Multi-Call and Multi-Transaction
The ape_ethereum
core plugin comes with a multicall
module containing tools for interacting with the multicall3 smart contract.
Multicall allows you to group function calls and transactions into a single call or transaction.
Here is an example of how you can use the multicall module:
import ape
from ape_ethereum import multicall
ADDRESSES = ("0xF4b8A02D4e8D76070bD7092B54D2cBbe90fa72e9", "0x80067013d7F7aF4e86b3890489AcAFe79F31a4Cb")
POOLS = [ape.project.IPool.at(a) for a in ADDRESSES]
def main():
# Use multi-call.
call = multicall.Call()
for pool in POOLS:
call.add(pool.getReserves)
print(list(call()))
# Use multi-transaction.
tx = multicall.Transaction()
for pool in POOLS:
tx.add(pool.ApplyDiscount, 123)
acct = ape.accounts.load("signer")
for result in tx(sender=acct):
print(result)