This commit is contained in:
writer 2024-10-15 12:26:21 +09:00
parent c2d0866191
commit db37d209ab
58 changed files with 4663 additions and 1043 deletions

View file

@ -0,0 +1,5 @@
## a place to dump wallets examples
<br>
* disclaimer: these boilerplates might not be my code, but rather well-known references.

View 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);
}
}

View file

@ -0,0 +1,180 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Let's create an multi-sig wallet. Here are the specifications.
// The wallet owners can
// - submit a transaction
// - approve and revoke approval of pending transcations
// - anyone can execute a transcation after enough owners has approved it.
contract MultiSigWallet {
event Deposit(address indexed sender, uint amount, uint balance);
event SubmitTransaction(
address indexed owner,
uint indexed txIndex,
address indexed to,
uint value,
bytes data
);
event ConfirmTransaction(address indexed owner, uint indexed txIndex);
event RevokeConfirmation(address indexed owner, uint indexed txIndex);
event ExecuteTransaction(address indexed owner, uint indexed txIndex);
address[] public owners;
mapping(address => bool) public isOwner;
uint public numConfirmationsRequired;
struct Transaction {
address to;
uint value;
bytes data;
bool executed;
uint numConfirmations;
}
// mapping from tx index => owner => bool
mapping(uint => mapping(address => bool)) public isConfirmed;
Transaction[] public transactions;
modifier onlyOwner() {
require(isOwner[msg.sender], "not owner");
_;
}
modifier txExists(uint _txIndex) {
require(_txIndex < transactions.length, "tx does not exist");
_;
}
modifier notExecuted(uint _txIndex) {
require(!transactions[_txIndex].executed, "tx already executed");
_;
}
modifier notConfirmed(uint _txIndex) {
require(!isConfirmed[_txIndex][msg.sender], "tx already confirmed");
_;
}
constructor(address[] memory _owners, uint _numConfirmationsRequired) {
require(_owners.length > 0, "owners required");
require(
_numConfirmationsRequired > 0 &&
_numConfirmationsRequired <= _owners.length,
"invalid number of required confirmations"
);
for (uint i = 0; i < _owners.length; i++) {
address owner = _owners[i];
require(owner != address(0), "invalid owner");
require(!isOwner[owner], "owner not unique");
isOwner[owner] = true;
owners.push(owner);
}
numConfirmationsRequired = _numConfirmationsRequired;
}
receive() external payable {
emit Deposit(msg.sender, msg.value, address(this).balance);
}
function submitTransaction(
address _to,
uint _value,
bytes memory _data
) public onlyOwner {
uint txIndex = transactions.length;
transactions.push(
Transaction({
to: _to,
value: _value,
data: _data,
executed: false,
numConfirmations: 0
})
);
emit SubmitTransaction(msg.sender, txIndex, _to, _value, _data);
}
function confirmTransaction(
uint _txIndex
) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) notConfirmed(_txIndex) {
Transaction storage transaction = transactions[_txIndex];
transaction.numConfirmations += 1;
isConfirmed[_txIndex][msg.sender] = true;
emit ConfirmTransaction(msg.sender, _txIndex);
}
function executeTransaction(
uint _txIndex
) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) {
Transaction storage transaction = transactions[_txIndex];
require(
transaction.numConfirmations >= numConfirmationsRequired,
"cannot execute tx"
);
transaction.executed = true;
(bool success, ) = transaction.to.call{value: transaction.value}(
transaction.data
);
require(success, "tx failed");
emit ExecuteTransaction(msg.sender, _txIndex);
}
function revokeConfirmation(
uint _txIndex
) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) {
Transaction storage transaction = transactions[_txIndex];
require(isConfirmed[_txIndex][msg.sender], "tx not confirmed");
transaction.numConfirmations -= 1;
isConfirmed[_txIndex][msg.sender] = false;
emit RevokeConfirmation(msg.sender, _txIndex);
}
function getOwners() public view returns (address[] memory) {
return owners;
}
function getTransactionCount() public view returns (uint) {
return transactions.length;
}
function getTransaction(
uint _txIndex
)
public
view
returns (
address to,
uint value,
bytes memory data,
bool executed,
uint numConfirmations
)
{
Transaction storage transaction = transactions[_txIndex];
return (
transaction.to,
transaction.value,
transaction.data,
transaction.executed,
transaction.numConfirmations
);
}
}

View file

@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract TestContract {
uint public i;
function callMe(uint j) public {
i += j;
}
function getData() public pure returns (bytes memory) {
return abi.encodeWithSignature("callMe(uint256)", 123);
}
}

View file

@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Payment channels allow participants to repeatedly transfer Ether off chain.
// Here is how this contract is used:
// - Alice deploys the contract, funding it with some Ether.
// - Alice authorizes a payment by signing a message (off chain) and sends the signature to Bob.
// - Bob claims his payment by presenting the signed message to the smart contract.
// - If Bob does not claim his payment, Alice get her Ether back after the contract expires
// This is called a uni-directional payment channel since the payment can go only in a single direction from Alice to Bob.
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/utils/cryptography/ECDSA.sol";
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.5/contracts/security/ReentrancyGuard.sol";
contract UniDirectionalPaymentChannel is ReentrancyGuard {
using ECDSA for bytes32;
address payable public sender;
address payable public receiver;
uint private constant DURATION = 7 * 24 * 60 * 60;
uint public expiresAt;
constructor(address payable _receiver) payable {
require(_receiver != address(0), "receiver = zero address");
sender = payable(msg.sender);
receiver = _receiver;
expiresAt = block.timestamp + DURATION;
}
function _getHash(uint _amount) private view returns (bytes32) {
// NOTE: sign with address of this contract to protect agains
// replay attack on other contracts
return keccak256(abi.encodePacked(address(this), _amount));
}
function getHash(uint _amount) external view returns (bytes32) {
return _getHash(_amount);
}
function _getEthSignedHash(uint _amount) private view returns (bytes32) {
return _getHash(_amount).toEthSignedMessageHash();
}
function getEthSignedHash(uint _amount) external view returns (bytes32) {
return _getEthSignedHash(_amount);
}
function _verify(uint _amount, bytes memory _sig) private view returns (bool) {
return _getEthSignedHash(_amount).recover(_sig) == sender;
}
function verify(uint _amount, bytes memory _sig) external view returns (bool) {
return _verify(_amount, _sig);
}
function close(uint _amount, bytes memory _sig) external nonReentrant {
require(msg.sender == receiver, "!receiver");
require(_verify(_amount, _sig), "invalid sig");
(bool sent, ) = receiver.call{value: _amount}("");
require(sent, "Failed to send Ether");
selfdestruct(sender);
}
function cancel() external {
require(msg.sender == sender, "!sender");
require(block.timestamp >= expiresAt, "!expired");
selfdestruct(sender);
}
}