Calling another contract

A contract function may invoke an external function of another contract.

Start by deploying the example contract in Writing StarkNet contracts (the compilation and deployment instructions can be found at the bottom of the page). Denote the address of this contract by BALANCE_CONTRACT.

In order to call this contract from another contract, define an interface by copying the declarations of the external functions:

@contract_interface
namespace IBalanceContract:
    func increase_balance(amount : felt):
    end

    func get_balance() -> (res : felt):
    end
end

Note that the body of the functions and the implicit arguments should be removed from the definitions.

You can use IBalanceContract.increase_balance() and IBalanceContract.get_balance() to invoke these functions on another contract. For example:

@external
func call_increase_balance{syscall_ptr : felt*, range_check_ptr}(
        contract_address : felt, amount : felt):
    IBalanceContract.increase_balance(
        contract_address=contract_address, amount=amount)
    return ()
end

@view
func call_get_balance{syscall_ptr : felt*, range_check_ptr}(
        contract_address : felt) -> (res : felt):
    let (res) = IBalanceContract.get_balance(
        contract_address=contract_address)
    return (res=res)
end

Note that calling a function of another contract requires passing one additional argument before the function’s original arguments – the address of the called contract. For example, IBalanceContract.increase_balance gets two arguments: contract_address and amount (rather than just amount). In addition, the syscall_ptr and the range_check_ptr implicit arguments are required.

Create a file named proxy_contract.cairo containing the interface declaration and the two functions call_increase_balance() and call_get_balance(), and deploy the contract. Denote the address of the new contract by PROXY_CONTRACT.

Now, invoke call_increase_balance with BALANCE_CONTRACT as the value of the contract_address argument. Make sure you replace PROXY_CONTRACT and BALANCE_CONTRACT with the addresses you got when you deployed the two contracts:

starknet invoke \
    --address PROXY_CONTRACT \
    --abi proxy_contract_abi.json \
    --function call_increase_balance \
    --inputs BALANCE_CONTRACT 10000

This will increase the balance stored in BALANCE_CONTRACT. Note that in our case, PROXY_CONTRACT does not have a storage of its own.

Wait until the transaction is added to a block, and then check the balance using the following two ways:

  1. Directly through BALANCE_CONTRACT

    starknet call \
        --address BALANCE_CONTRACT \
        --abi balance_contract_abi.json \
        --function get_balance
    
  2. Indirectly through PROXY_CONTRACT

    starknet call \
        --address PROXY_CONTRACT \
        --abi proxy_contract_abi.json \
        --function call_get_balance \
        --inputs BALANCE_CONTRACT
    

Both commands should return 10000.

Getting the current contract’s address

You can get the current contract’s address by using the get_contract_address() library function.

from starkware.starknet.common.syscalls import (
    get_contract_address)

# ...

let (contract_address) = get_contract_address()

The above is similar to address(this) in Solidity.

Delegate calls

A delegate call is a way to invoke a function declared in another contract within the context of the calling contract.

In particular, storage-changing operations in the invoked function will change the state of the calling contract instead of affecting the state of the contract containing the function itself. Similarly, get_caller_address() and get_contract_address() will return the same value they would have returned if they were called from the calling function

To perform a delegate call, use the contract interface as above, but prepend delegate_ to the function name.

# Define local balance variable in our proxy contract.
@storage_var
func balance() -> (res : felt):
end

@external
func increase_my_balance{syscall_ptr : felt*, range_check_ptr}(
        other_contract_address : felt, amount : felt):
    # Increase the local balance variable using a function from a different contract by adding
    # delegate_ before the function name.
    IBalanceContract.delegate_increase_balance(
        contract_address=other_contract_address, amount=amount)
    return ()
end

Invoking increase_my_balance will increase the balance in the calling contract’s storage using the increase_balance() function of the other contract.

Unlike a regular contract call, here the balance of the calling contract (rather than the called contract) is modified.

Note: you can use delegate call to invoke a function that changes a storage variable which wasn’t defined in the calling contract. In such a case, the new corresponding storage variable will be created in the calling contract, but it won’t be easily accessible (you can access it by a second delegate call, or directly using storage_read()).