(await contract.ping()).toString();  //-> 999999999  ( correct )
ping() is a view function - you can just call it without creating a transaction. So ethers.js doesn't create a transaction and just returns result of the call.
(await contract.ping2()).toString(); //-> [object Object] ( ?? )
Why does using sendTransaction also return [object Object] ?
ping2() is a regular public function. Which suggests that you need to create a transaction to execute it (even though in this case it doesn't make any state changes so it could be a view function as well).
Ethers.js returns the transaction data instead of the contract function return value, when you're creating a transaction.
There are few ways to read values that the transaction produced using Ethers.js.
- In this case, - ping2()doesn't make any state changes and doesn't even read any blockchain data, so it could be a- purefunction. If it were reading blockchain data, it would be a- viewfunction... In both cases,- ethers.jsreturns the result of the function call (not tx).
 
- Transaction to a setter and calling a getter. - contract MyContract {
    uint256 value;
    function setValue(uint256 _value) public {
        value = _value;
    }
    function getValue() public view returns (uint256) {
        return value;
    }
}
 - First you create a transaction that executes the - setValue()function, and then you make a call to- getValue()(without transaction, so it returns the value in your JS).
 
- Reading event logs that your transaction produced - event Transfer(address from, address to, uint256 amount);
function transfer(address _to, uint256 _amount) public {
    emit Transfer(msg.sender, _to, _amount);
}
 - You can get the transaction receipt that also contains the event logs (in this case, the - Transferevent and its values).