mirror of
https://github.com/autistic-symposium/web3-starter-sol.git
synced 2025-07-26 16:35:31 -04:00
Clean up
This commit is contained in:
parent
c2d0866191
commit
db37d209ab
58 changed files with 4663 additions and 1043 deletions
5
advanced_knowledge/wallets/README.md
Normal file
5
advanced_knowledge/wallets/README.md
Normal 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.
|
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);
|
||||
}
|
||||
}
|
180
advanced_knowledge/wallets/multisig.sol
Normal file
180
advanced_knowledge/wallets/multisig.sol
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
14
advanced_knowledge/wallets/send_tx_multisig.sol
Normal file
14
advanced_knowledge/wallets/send_tx_multisig.sol
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue