brightLink 3: Aave connection with multiple users

If you like this content consider tipping in ETH at this address: 0xf9f6849230cBe10200B43dB103511b778898e71C

Code associated with this post is available on Github (for this post, see Tag v0.0.2)

In the previous post, the BrightLink contract received some funds, moved them to Aave, accrued some interest and then returned the new balance back to the contract owner. However, cycling money through pools for the benefit of a single user is not that useful (they may as well just put their money directly into an Aave pool). For this project, the contract is used as escrow for a donor, and there is a customer who may or may not receive the donated funds depending on ingested data. Then there is a contract owner who will make some profit from the funds being parked in Aave. Therefore, the contract needs to be updated to handle all three actors.

First thing to do is to setup some new accounts for the customer and the donor. As a reminder, the aim of this project is to incentivize environmental projects where a donor submits funds into escrow in the contract and a community organization aims to brighten their local environment in a way that can be measured remotely. If they succeed they get a payout of the donated funds. If not, the donor gets them back. Either way, between the donation and the payout, the contract owner profits by parking the donated funds in an aave pool and keeping the accrued interest. The actor making the initial deposit will be referred to as the “donor”, the organization vying for a payout is the “customer”.

Add two new accounts to MetaMask by unlocking the MetaMask browser extension, clicking the “create account” button and providing account names. It is convenient to add these accounts to brownie. To do this, first export the private key for each account and copy to the clipboard. Then in the terminal:

$ brownie accounts new donor

>> enter private key
>> enter password to unlock account

$ brownie accounts new customer

>> enter private key
>> enter password to unlock account

With these new accounts set up alongside the existing account, we can start developing an updated contract that interacts with all three.

Contract

The contract will require addresses to be defined for the two new contracts, so these can be defined (but not instantiated) before defining the constructor function. These addresses also need to be passed to the contract as parameters of the constructor. I like to use the convention of having the same name for parameters and variables except that the parameters are prepended by an underscore. Then, inside the constructor the variable is instantiated with the value passed to the constructor at deployment. The variable definitions and constructor therefore look like this:

address customer;
address donor;// SPDX-License-Identifier: MIT
// this contract is deployed on Kovan at 0xb7A997C957bF86E82ea5804c301142eF07c36829

pragma solidity ^0.6.6;

import "@chainlink/contracts/src/v0.6/ChainlinkClient.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "/home/joe/Code/BrightLink/interfaces/ILendingPoolAddressesProviderV2.sol";
import "/home/joe/Code/BrightLink/interfaces/ILendingPoolV2.sol";

contract BrightLink_Aave_v2 {

    uint256 depositedFunds;
    address private owner;
    uint16 referral = 0;
    address dai_address;
    address adai_address;
    address poolAddress;
    address poolAddressProvider;
    address customer;
    address donor;
    uint16 threshold;
    uint16 value;
    IERC20 dai;
    IERC20 adai;
    ILendingPoolV2 lendingPool;
    ILendingPoolAddressesProviderV2 provider;


    constructor(address _dai_address, address _adai_address, address _poolAddressProvider, address _customer, address _donor, uint16 _threshold) public{
        
        owner = msg.sender;
        customer = _customer;
        donor = _donor;
        depositedFunds = 0;
        dai_address = _dai_address;
        adai_address = _adai_address;
        poolAddressProvider = _poolAddressProvider;
        threshold = _threshold;
        dai = IERC20(dai_address);
        adai = IERC20(adai_address);
        provider = ILendingPoolAddressesProviderV2(poolAddressProvider); 
        poolAddress = provider.getLendingPool();
        lendingPool = ILendingPoolV2(poolAddress);

    }

The constructor includes the information that is defined by the contract owner at the time of deployment. This now includes the various wallet addresses, the contract addresses for the relevant tokens, and the threshold value that determines a payout. Notice that the variable “owner” is instantiated to “msg.sender” which is used later to ensure that only the contract owner can access certain functions.

No changes are required to the functions that move money between the contract and Aave because the new players are not involved. However, after the deposit has been retrieved from Aave, changes to the contract are required to ensure the right amount of DAI is transferred to the right wallets. This is controlled by the retrieveDAI() function. However, I did decide to update the way the contract logs the deposited funds. Now, this is done in a specific function:

function lockDepositBalance() public onlyOwner{

    depositedFunds = dai.balanceOf(address(this));
}

This new function can be called by the contract owner once the total donated amount of DAI has been transferred into the contract, the owner can set the contract balance as the deposited amount. This should be set before depositing the funds into Aave, because the final contract balance less this value gives the profit to be sent to the contract owner. This function allows several deposits to be made and aggregated provided they all occur before the fund is sent to Aave.

In this version, the address receiving the DAI will be different depending on the value of the trigger (in this case the trigger value is just a dummy, but ultimately it will come from a Chainlink oracle linked to a remote sensing app). If the threshold value (set in constructor) is exceeded by the trigger value then the customer has achieved their objective and the DAI should be transferred to their wallet. The total amount of DAI is greater than the deposit thanks to interest accrued in Aave and that difference should be transferred to the contract owner. On the other hand, if the threshold exceed the trigger value, the deposit amount should be transferred back to the donor and the profit should still go to the contract owner. This can be achieved with a simple “if/else” conditional statement:

function retrieveDAI() public onlyOwner{
        
    // send DAI from contract back to owner
    function retrieveDAI() public onlyOwner{
        
        // send DAI from contract back to owner
        dai.approve(address(this), depositedFunds);

        if (value > threshold){
            
            require(dai.transfer(customer, depositedFunds));
            
            if (dai.balanceOf(address(this))>0){
                require(dai.transfer(owner,dai.balanceOf(address(this))));
            }
        }

        else{

            require(dai.transfer(donor, depositedFunds));
            
            if (dai.balanceOf(address(this))>0){
                require(dai.transfer(owner, dai.balanceOf(address(this))));
            }
        }

    }

These changes are sufficient to achieve the new functionality, so we can deploy and test…

deploy

The new contract can be deployed by updating the Python scripts developed in the previous post. The new deploy script should look like this:

# deploy_aave_v2.py

#!/usr/bin/python3
from brownie import BrightLink_Aave_v2, accounts, network, config


def main():

    owner = load_account('main') # load account
    customer = load_account('account2')
    donor = load_account('account3')
    dai_address = config["networks"][network.show_active()]["dai_address"]
    adai_address = config["networks"][network.show_active()]["adai_address"]
    poolAddressProvider = config["networks"][network.show_active()]["poolAddressProvider"]
    threshold = 5

    deploy_contract(owner,dai_address,adai_address,poolAddressProvider,customer,donor,threshold)

    return


def load_account(accountName):

    account = accounts.load(accountName)

    return account


def  deploy_contract(owner, dai_address, adai_address, poolAddressProvider, customer, donor, threshold):

    print("Deploying contract to {} network".format(network.show_active()))
                 
BrightLink_Aave_v2.deploy(dai_address,adai_address,poolAddressProvider,customer,donor,threshold,{'from':owner})

    return

Alternatively, the contract can be deployed using brownie console. Either way, we can do some basic testing of the new functionality from the console. First, we can check that there is profit generated for the contract owner by depositing some funds in Aave and repeatedly checking the debt token balance. An increasing aDAI balance indicates that there are gradually more and more aDAI that can eventually be sapped 1:1 for DAI, meaning a profit can be returned. This was achieved as follows:

This image shows a transfer of DAI from the donor, then the owner triggers deposit of the funds to Aave. Then, periodic calls to the function's checkbalance() function shows that the aDAI balance is gradually increasing thanks to interest from Aave.

In the terminal above, I sent 5000 DAI from the donor to the contract, then used the contract’s depositFundsToAave() function to send 5000 DAI to an Aave pool. This returned aDAI to the contract. This aDAI balance is shown as the second value returned by the contract’s checkBalance() function. Calling this function multiple times returned a larger value each time, confirming that this is generating a profit for the contract owner.

The next action to test is the distribution of funds to the correct wallets. This was achieved in the console as follows:

In this terminal a 2000 DAI transfer from the donor to the contract was made, then the contract’s depositFundsToAave() function was used to move the DAI to the lending pool.
In this terminal view I checked the aDAI balance to confirm profit had been generated, then withdrew finds from the Aave pool back into the contract. The dummy value was then set using the setDummyTrigger() function. Finally, the retrieveDAI() function was used to redistribute the funds to the three linked wallets.

In the second terminal view above I showed the wallet balances at the end of the transactions to confirm correct fund distribution. I set the trigger value to 7 and the threshold to 5. Because the trigger exceeds the threshold I expected the customer to receive the initial deposit balance (2000 DAI) to their wallet and for the donor’s balance to have decremented by 2000 DAI relative to the initial balance. The three balances above show this occurred.

testing

The console testing above showed the basic functionality of the contract, but automated testing is required for better coverage, transparency and repeatability. This was achieved by updating the testing scripts developed in the previous post. The testing is fairly self explanatory, and the only function that required modification was test_withdrawal_from_contract(), although the new accounts were also added to conftest.py. Note that this version only tests the contract with single values set in conftest.py. Integration tests that iterate through ranges of variable values, increasing test coverage, are coming in the next post.

def test_withdrawal_from_contract(set_deposit_amount, getDeployedContract, load_owner, load_customer, load_donor, set_threshold, set_trigger_value):

    dai = interface.IERC20('0xFf795577d9AC8bD7D90Ee22b6C1703490b6512FD')
    adai = interface.IERC20('0xdCf0aF9e59C002FA3AA091a46196b37530FD48a8')
    contract = getDeployedContract
    contract.setDummyTrigger(set_trigger_value,{'from':load_owner})

    assert adai.balanceOf(contract) == 0
    assert dai.balanceOf(contract) > set_deposit_amount

    initialOwnerBalance = dai.balanceOf(load_owner)
    initialCustomerBalance = dai.balanceOf(load_customer)
    initialDonorBalance = dai.balanceOf(load_donor)

    contract.retrieveDAI({'from':load_owner})
    
    time.sleep(20)

    if set_trigger_value > set_threshold:

        assert adai.balanceOf(contract) == 0
        assert dai.balanceOf(contract) == 0
        assert dai.balanceOf(load_owner) > initialOwnerBalance
        assert dai.balanceOf(load_customer) == initialCustomerBalance+set_deposit_amount
        assert dai.balanceOf(load_donor) == initialDonorBalance
    
    else:
        assert adai.balanceOf(contract) == 0
        assert dai.balanceOf(contract) == 0
        assert dai.balanceOf(load_owner) > initialOwnerBalance
        assert dai.balanceOf(load_customer) == initialCustomerBalance
        assert dai.balanceOf(load_donor) == initialDonorBalance+set_deposit_amount

    return

Using brownie to run the tests:

$ brownie test --network kovan

should return 5 passes:

2 thoughts on “brightLink 3: Aave connection with multiple users

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s