Canal de Pagamento Bidirecional

# Canal de Pagamento Bidirecional

Canais de pagamento bidirecional permite que os participantes Alice e Bob transfiram Ether off chain repetidamente.

Pagamentos podem ser feitos em ambas direções, Alice paga Bob e Bob paga Alice.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
pragma experimental ABIEncoderV2;

/*
Abrindo um canal
1. Alice e Bob depositam em uma carteira multi-sig
2. Endereço de canal de pagamento é pré-computado
3. Alice e Bob trocam assinaturas dos balanços iniciais
4. Alice e Bob criam uma transação que pode implementar um canal de pagamento da
carteira multi-sig
Atualizar saldos do canal
1. Repete os passos 1 - 3 da abertura do canal
2. Da carteira multi-sig cria uma transação que vai
   - apagar a transação que teria implementado o canal de pagamento antigo
   - e depois cria a transação que pode implementar um canal de pagamento 
     com novos balanços

Fechando um canal quando Alice e Bob concordam com o saldo final
1. Da carteira multi-sig cria uma transação que vai
   - enviar pagamentos para Alice e Bob
   - e depois apagar a transação que teria criado o canal de pagamento

Fechando um canal quando Alice e Bob não concordam com os saldos finais
1. Implementa canal de pagamento da multi-sig
2. chama challengeExit()para iniciar o processo de fechamento do canal
3. Alice e Bob podem retirar fundos uma vez que o canal é extinto
*/

import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/math/SafeMath.sol";
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.3/contracts/cryptography/ECDSA.sol";

contract BiDirectionalPaymentChannel {
    using SafeMath for uint;
    using ECDSA for bytes32;

    event ChallengeExit(address indexed sender, uint nonce);
    event Withdraw(address indexed to, uint amount);

    address payable[2] public users;
    mapping(address => bool) public isUser;

    mapping(address => uint) public balances;

    uint public challengePeriod;
    uint public expiresAt;
    uint public nonce;

    modifier checkBalances(uint[2] memory _balances) {
        require(
            address(this).balance >= _balances[0].add(_balances[1]),
            "balance of contract must be >= to the total balance of users"
        );
        _;
    }

    // NOTA: depositar de uma carteira multi-sig
    constructor(
        address payable[2] memory _users,
        uint[2] memory _balances,
        uint _expiresAt,
        uint _challengePeriod
    ) payable checkBalances(_balances) {
        require(_expiresAt > block.timestamp, "Expiration must be > now");
        require(_challengePeriod > 0, "Challenge period must be > 0");

        for (uint i = 0; i < _users.length; i++) {
            address payable user = _users[i];

            require(!isUser[user], "user must be unique");
            users[i] = user;
            isUser[user] = true;

            balances[user] = _balances[i];
        }

        expiresAt = _expiresAt;
        challengePeriod = _challengePeriod;
    }

    function verify(
        bytes[2] memory _signatures,
        address _contract,
        address[2] memory _signers,
        uint[2] memory _balances,
        uint _nonce
    ) public pure returns (bool) {
        for (uint i = 0; i < _signatures.length; i++) {
            /*
            NOTA: assina com endereço desse contrato para proteger
                  contra ataque de repetição em outros contratos
            */
            bool valid = _signers[i] ==
                keccak256(abi.encodePacked(_contract, _balances, _nonce))
                .toEthSignedMessageHash()
                .recover(_signatures[i]);

            if (!valid) {
                return false;
            }
        }

        return true;
    }

    modifier checkSignatures(
        bytes[2] memory _signatures,
        uint[2] memory _balances,
        uint _nonce
    ) {
        // Nota: copia a matriz de armazenamento para a memória
        address[2] memory signers;
        for (uint i = 0; i < users.length; i++) {
            signers[i] = users[i];
        }

        require(
            verify(_signatures, address(this), signers, _balances, _nonce),
            "Invalid signature"
        );

        _;
    }

    modifier onlyUser() {
        require(isUser[msg.sender], "Not user");
        _;
    }

    function challengeExit(
        uint[2] memory _balances,
        uint _nonce,
        bytes[2] memory _signatures
    )
        public
        onlyUser
        checkSignatures(_signatures, _balances, _nonce)
        checkBalances(_balances)
    {
        require(block.timestamp < expiresAt, "Expired challenge period");
        require(_nonce > nonce, "Nonce must be greater than the current nonce");

        for (uint i = 0; i < _balances.length; i++) {
            balances[users[i]] = _balances[i];
        }

        nonce = _nonce;
        expiresAt = block.timestamp.add(challengePeriod);

        emit ChallengeExit(msg.sender, nonce);
    }

    function withdraw() public onlyUser {
        require(block.timestamp >= expiresAt, "Challenge period has not expired yet");

        uint amount = balances[msg.sender];
        balances[msg.sender] = 0;

        (bool sent, ) = msg.sender.call{value: amount}("");
        require(sent, "Failed to send Ether");

        emit Withdraw(msg.sender, amount);
    }
}
Last Updated: 7/10/2022, 21:58:58