mirror of
https://github.com/autistic-symposium/web3-starter-sol.git
synced 2025-08-11 16:10:05 -04:00
Clean up
This commit is contained in:
parent
c2d0866191
commit
db37d209ab
58 changed files with 4663 additions and 1043 deletions
167
advanced_knowledge/wallets/bidirectional_payment_channel.sol
Normal file
167
advanced_knowledge/wallets/bidirectional_payment_channel.sol
Normal file
|
@ -0,0 +1,167 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
/*
|
||||
|
||||
Bi-directional payment channels allow participants Alice and Bob to repeatedly transfer Ether off chain.
|
||||
|
||||
Payments can go both ways, Alice pays Bob and Bob pays Alice.
|
||||
|
||||
Opening a channel
|
||||
1. Alice and Bob fund a multi-sig wallet
|
||||
2. Precompute payment channel address
|
||||
3. Alice and Bob exchanges signatures of initial balances
|
||||
4. Alice and Bob creates a transaction that can deploy a payment channel from
|
||||
the multi-sig wallet
|
||||
|
||||
Update channel balances
|
||||
1. Repeat steps 1 - 3 from opening a channel
|
||||
2. From multi-sig wallet create a transaction that will
|
||||
- delete the transaction that would have deployed the old payment channel
|
||||
- and then create a transaction that can deploy a payment channel with the
|
||||
new balances
|
||||
|
||||
Closing a channel when Alice and Bob agree on the final balance
|
||||
1. From multi-sig wallet create a transaction that will
|
||||
- send payments to Alice and Bob
|
||||
- and then delete the transaction that would have created the payment channel
|
||||
|
||||
Closing a channel when Alice and Bob do not agree on the final balances
|
||||
1. Deploy payment channel from multi-sig
|
||||
2. call challengeExit() to start the process of closing a channel
|
||||
3. Alice and Bob can withdraw funds once the channel is expired
|
||||
*/
|
||||
|
||||
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol";
|
||||
|
||||
contract BiDirectionalPaymentChannel {
|
||||
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] + _balances[1],
|
||||
"balance of contract must be >= to the total balance of users"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
// NOTE: deposit from multi-sig wallet
|
||||
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++) {
|
||||
/*
|
||||
NOTE: sign with address of this contract to protect
|
||||
agains replay attack on other contracts
|
||||
*/
|
||||
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
|
||||
) {
|
||||
// Note: copy storage array to memory
|
||||
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 + 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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue