Fundamentals

Chapter 5 / 15

External Contract Calls

interface, @non_reentrant, and the CEI pattern.


Calling other contracts is unavoidable in DeFi. Covenant's interface keyword declares the external ABI, and @non_reentrant inserts a mutex at the bytecode level. The Checks-Effects-Interactions pattern is enforced by convention and auditable via OMEGA lint rule CEI-001.

interfaces.cov
// Declare the external contract's ABI
interface IERC20 {
    action transfer(to: address, amount: u256) -> bool;
    action transferFrom(from: address, to: address, amount: u256) -> bool;
    view balanceOf(who: address) -> u256;
    view allowance(owner: address, spender: address) -> u256;
}

interface IUniswapV2Pair {
    action swap(
        amount0Out: u256,
        amount1Out: u256,
        to: address,
        data: bytes
    );
    view getReserves() -> (u112, u112, u32);
}
vault.cov — non-reentrant external calls
record Vault {
    token:   address;
    shares:  map(address => u256);
    total:   u256;

    error NothingToWithdraw();

    // @non_reentrant inserts an on-chain mutex (SSTORE lock slot)
    @non_reentrant
    action deposit(amount: u256) {
        let erc20 = IERC20(self.token);

        // Checks
        // (amount > 0 validated by the caller passing a nonzero value)

        // Effects — update state BEFORE external call
        let new_shares = amount;   // simplified 1:1 for illustration
        self.shares[msg.sender] += new_shares;
        self.total += amount;
        emit Deposited(msg.sender, amount, new_shares);

        // Interactions — external call LAST
        erc20.transferFrom(msg.sender, self.addr(), amount);
    }

    @non_reentrant
    action withdraw() {
        let user_shares = self.shares[msg.sender];
        if user_shares == 0 {
            revert_with NothingToWithdraw();
        }

        // Effects before Interactions
        self.shares[msg.sender] = 0;
        self.total -= user_shares;
        emit Withdrawn(msg.sender, user_shares);

        // Interaction last
        IERC20(self.token).transfer(msg.sender, user_shares);
    }

    view share_of(who: address) -> u256 {
        return self.shares[who];
    }

    event Deposited(who: address, amount: u256, shares: u256);
    event Withdrawn(who: address, shares: u256);
}

Annotations

interface I { ... }declares an external contract type. Calling I(addr).method() compiles to a CALL with the correct 4-byte selector.
@non_reentrantinserts a transient storage mutex (EIP-1153 where available, SSTORE otherwise). Reverts if the function is re-entered.
self.addr()returns the contract's own address — equivalent to Solidity's address(this).
CEI patternis not enforced at compile time in V0.7, but OMEGA lint rule CEI-001 flags violations. Future versions will enforce it statically.

Key takeaways