mirror of
https://github.com/autistic-symposium/web3-starter-sol.git
synced 2025-07-23 15:10:48 -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/applications_contracts/README.md
Normal file
5
advanced_knowledge/applications_contracts/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
## a place to dump application contracts
|
||||
|
||||
<br>
|
||||
|
||||
* disclaimer: these boilerplates might not be my code, but rather well-known references.
|
136
advanced_knowledge/applications_contracts/crowd_fund.sol
Normal file
136
advanced_knowledge/applications_contracts/crowd_fund.sol
Normal file
|
@ -0,0 +1,136 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
/*
|
||||
Crowd fund ERC20 token
|
||||
|
||||
User creates a campaign.
|
||||
Users can pledge, transferring their token to a campaign.
|
||||
After the campaign ends, campaign creator can claim the funds if total amount pledged is more than the campaign goal.
|
||||
Otherwise, campaign did not reach it's goal, users can withdraw their pledge.
|
||||
*/
|
||||
|
||||
|
||||
interface IERC20 {
|
||||
function transfer(address, uint) external returns (bool);
|
||||
|
||||
function transferFrom(address, address, uint) external returns (bool);
|
||||
}
|
||||
|
||||
contract CrowdFund {
|
||||
event Launch(
|
||||
uint id,
|
||||
address indexed creator,
|
||||
uint goal,
|
||||
uint32 startAt,
|
||||
uint32 endAt
|
||||
);
|
||||
event Cancel(uint id);
|
||||
event Pledge(uint indexed id, address indexed caller, uint amount);
|
||||
event Unpledge(uint indexed id, address indexed caller, uint amount);
|
||||
event Claim(uint id);
|
||||
event Refund(uint id, address indexed caller, uint amount);
|
||||
|
||||
struct Campaign {
|
||||
// Creator of campaign
|
||||
address creator;
|
||||
// Amount of tokens to raise
|
||||
uint goal;
|
||||
// Total amount pledged
|
||||
uint pledged;
|
||||
// Timestamp of start of campaign
|
||||
uint32 startAt;
|
||||
// Timestamp of end of campaign
|
||||
uint32 endAt;
|
||||
// True if goal was reached and creator has claimed the tokens.
|
||||
bool claimed;
|
||||
}
|
||||
|
||||
IERC20 public immutable token;
|
||||
// Total count of campaigns created.
|
||||
// It is also used to generate id for new campaigns.
|
||||
uint public count;
|
||||
// Mapping from id to Campaign
|
||||
mapping(uint => Campaign) public campaigns;
|
||||
// Mapping from campaign id => pledger => amount pledged
|
||||
mapping(uint => mapping(address => uint)) public pledgedAmount;
|
||||
|
||||
constructor(address _token) {
|
||||
token = IERC20(_token);
|
||||
}
|
||||
|
||||
function launch(uint _goal, uint32 _startAt, uint32 _endAt) external {
|
||||
require(_startAt >= block.timestamp, "start at < now");
|
||||
require(_endAt >= _startAt, "end at < start at");
|
||||
require(_endAt <= block.timestamp + 90 days, "end at > max duration");
|
||||
|
||||
count += 1;
|
||||
campaigns[count] = Campaign({
|
||||
creator: msg.sender,
|
||||
goal: _goal,
|
||||
pledged: 0,
|
||||
startAt: _startAt,
|
||||
endAt: _endAt,
|
||||
claimed: false
|
||||
});
|
||||
|
||||
emit Launch(count, msg.sender, _goal, _startAt, _endAt);
|
||||
}
|
||||
|
||||
function cancel(uint _id) external {
|
||||
Campaign memory campaign = campaigns[_id];
|
||||
require(campaign.creator == msg.sender, "not creator");
|
||||
require(block.timestamp < campaign.startAt, "started");
|
||||
|
||||
delete campaigns[_id];
|
||||
emit Cancel(_id);
|
||||
}
|
||||
|
||||
function pledge(uint _id, uint _amount) external {
|
||||
Campaign storage campaign = campaigns[_id];
|
||||
require(block.timestamp >= campaign.startAt, "not started");
|
||||
require(block.timestamp <= campaign.endAt, "ended");
|
||||
|
||||
campaign.pledged += _amount;
|
||||
pledgedAmount[_id][msg.sender] += _amount;
|
||||
token.transferFrom(msg.sender, address(this), _amount);
|
||||
|
||||
emit Pledge(_id, msg.sender, _amount);
|
||||
}
|
||||
|
||||
function unpledge(uint _id, uint _amount) external {
|
||||
Campaign storage campaign = campaigns[_id];
|
||||
require(block.timestamp <= campaign.endAt, "ended");
|
||||
|
||||
campaign.pledged -= _amount;
|
||||
pledgedAmount[_id][msg.sender] -= _amount;
|
||||
token.transfer(msg.sender, _amount);
|
||||
|
||||
emit Unpledge(_id, msg.sender, _amount);
|
||||
}
|
||||
|
||||
function claim(uint _id) external {
|
||||
Campaign storage campaign = campaigns[_id];
|
||||
require(campaign.creator == msg.sender, "not creator");
|
||||
require(block.timestamp > campaign.endAt, "not ended");
|
||||
require(campaign.pledged >= campaign.goal, "pledged < goal");
|
||||
require(!campaign.claimed, "claimed");
|
||||
|
||||
campaign.claimed = true;
|
||||
token.transfer(campaign.creator, campaign.pledged);
|
||||
|
||||
emit Claim(_id);
|
||||
}
|
||||
|
||||
function refund(uint _id) external {
|
||||
Campaign memory campaign = campaigns[_id];
|
||||
require(block.timestamp > campaign.endAt, "not ended");
|
||||
require(campaign.pledged < campaign.goal, "pledged >= goal");
|
||||
|
||||
uint bal = pledgedAmount[_id][msg.sender];
|
||||
pledgedAmount[_id][msg.sender] = 0;
|
||||
token.transfer(msg.sender, bal);
|
||||
|
||||
emit Refund(_id, msg.sender, bal);
|
||||
}
|
||||
}
|
63
advanced_knowledge/applications_contracts/dutch_auction.sol
Normal file
63
advanced_knowledge/applications_contracts/dutch_auction.sol
Normal file
|
@ -0,0 +1,63 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
/*
|
||||
Dutch auction for NFT.
|
||||
|
||||
Auction
|
||||
Seller of NFT deploys this contract setting a starting price for the NFT.
|
||||
Auction lasts for 7 days.
|
||||
Price of NFT decreases over time.
|
||||
Participants can buy by depositing ETH greater than the current price computed by the smart contract.
|
||||
Auction ends when a buyer buys the NFT.
|
||||
*/
|
||||
|
||||
interface IERC721 {
|
||||
function transferFrom(address _from, address _to, uint _nftId) external;
|
||||
}
|
||||
|
||||
contract DutchAuction {
|
||||
uint private constant DURATION = 7 days;
|
||||
|
||||
IERC721 public immutable nft;
|
||||
uint public immutable nftId;
|
||||
|
||||
address payable public immutable seller;
|
||||
uint public immutable startingPrice;
|
||||
uint public immutable startAt;
|
||||
uint public immutable expiresAt;
|
||||
uint public immutable discountRate;
|
||||
|
||||
constructor(uint _startingPrice, uint _discountRate, address _nft, uint _nftId) {
|
||||
seller = payable(msg.sender);
|
||||
startingPrice = _startingPrice;
|
||||
startAt = block.timestamp;
|
||||
expiresAt = block.timestamp + DURATION;
|
||||
discountRate = _discountRate;
|
||||
|
||||
require(_startingPrice >= _discountRate * DURATION, "starting price < min");
|
||||
|
||||
nft = IERC721(_nft);
|
||||
nftId = _nftId;
|
||||
}
|
||||
|
||||
function getPrice() public view returns (uint) {
|
||||
uint timeElapsed = block.timestamp - startAt;
|
||||
uint discount = discountRate * timeElapsed;
|
||||
return startingPrice - discount;
|
||||
}
|
||||
|
||||
function buy() external payable {
|
||||
require(block.timestamp < expiresAt, "auction expired");
|
||||
|
||||
uint price = getPrice();
|
||||
require(msg.value >= price, "ETH < price");
|
||||
|
||||
nft.transferFrom(seller, msg.sender, nftId);
|
||||
uint refund = msg.value - price;
|
||||
if (refund > 0) {
|
||||
payable(msg.sender).transfer(refund);
|
||||
}
|
||||
selfdestruct(seller);
|
||||
}
|
||||
}
|
101
advanced_knowledge/applications_contracts/english_auction.sol
Normal file
101
advanced_knowledge/applications_contracts/english_auction.sol
Normal file
|
@ -0,0 +1,101 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17
|
||||
|
||||
/*
|
||||
English auction for NFT.
|
||||
|
||||
Auction
|
||||
Seller of NFT deploys this contract.
|
||||
Auction lasts for 7 days.
|
||||
Participants can bid by depositing ETH greater than the current highest bidder.
|
||||
All bidders can withdraw their bid if it is not the current highest bid.
|
||||
After the auction
|
||||
Highest bidder becomes the new owner of NFT.
|
||||
The seller receives the highest bid of ETH.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
interface IERC721 {
|
||||
function safeTransferFrom(address from, address to, uint tokenId) external;
|
||||
|
||||
function transferFrom(address, address, uint) external;
|
||||
}
|
||||
|
||||
contract EnglishAuction {
|
||||
event Start();
|
||||
event Bid(address indexed sender, uint amount);
|
||||
event Withdraw(address indexed bidder, uint amount);
|
||||
event End(address winner, uint amount);
|
||||
|
||||
IERC721 public nft;
|
||||
uint public nftId;
|
||||
|
||||
address payable public seller;
|
||||
uint public endAt;
|
||||
bool public started;
|
||||
bool public ended;
|
||||
|
||||
address public highestBidder;
|
||||
uint public highestBid;
|
||||
mapping(address => uint) public bids;
|
||||
|
||||
constructor(address _nft, uint _nftId, uint _startingBid) {
|
||||
nft = IERC721(_nft);
|
||||
nftId = _nftId;
|
||||
|
||||
seller = payable(msg.sender);
|
||||
highestBid = _startingBid;
|
||||
}
|
||||
|
||||
function start() external {
|
||||
require(!started, "started");
|
||||
require(msg.sender == seller, "not seller");
|
||||
|
||||
nft.transferFrom(msg.sender, address(this), nftId);
|
||||
started = true;
|
||||
endAt = block.timestamp + 7 days;
|
||||
|
||||
emit Start();
|
||||
}
|
||||
|
||||
function bid() external payable {
|
||||
require(started, "not started");
|
||||
require(block.timestamp < endAt, "ended");
|
||||
require(msg.value > highestBid, "value < highest");
|
||||
|
||||
if (highestBidder != address(0)) {
|
||||
bids[highestBidder] += highestBid;
|
||||
}
|
||||
|
||||
highestBidder = msg.sender;
|
||||
highestBid = msg.value;
|
||||
|
||||
emit Bid(msg.sender, msg.value);
|
||||
}
|
||||
|
||||
function withdraw() external {
|
||||
uint bal = bids[msg.sender];
|
||||
bids[msg.sender] = 0;
|
||||
payable(msg.sender).transfer(bal);
|
||||
|
||||
emit Withdraw(msg.sender, bal);
|
||||
}
|
||||
|
||||
function end() external {
|
||||
require(started, "not started");
|
||||
require(block.timestamp >= endAt, "not ended");
|
||||
require(!ended, "ended");
|
||||
|
||||
ended = true;
|
||||
if (highestBidder != address(0)) {
|
||||
nft.safeTransferFrom(address(this), highestBidder, nftId);
|
||||
seller.transfer(highestBid);
|
||||
} else {
|
||||
nft.safeTransferFrom(address(this), seller, nftId);
|
||||
}
|
||||
|
||||
emit End(highestBidder, highestBid);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
// An example of calling multiple functions with a single transaction, using delegatecall.
|
||||
|
||||
contract MultiDelegatecall {
|
||||
error DelegatecallFailed();
|
||||
|
||||
function multiDelegatecall(
|
||||
bytes[] memory data
|
||||
) external payable returns (bytes[] memory results) {
|
||||
results = new bytes[](data.length);
|
||||
|
||||
for (uint i; i < data.length; i++) {
|
||||
(bool ok, bytes memory res) = address(this).delegatecall(data[i]);
|
||||
if (!ok) {
|
||||
revert DelegatecallFailed();
|
||||
}
|
||||
results[i] = res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Why use multi delegatecall? Why not multi call?
|
||||
// alice -> multi call --- call ---> test (msg.sender = multi call)
|
||||
// alice -> test --- delegatecall ---> test (msg.sender = alice)
|
||||
contract TestMultiDelegatecall is MultiDelegatecall {
|
||||
event Log(address caller, string func, uint i);
|
||||
|
||||
function func1(uint x, uint y) external {
|
||||
// msg.sender = alice
|
||||
emit Log(msg.sender, "func1", x + y);
|
||||
}
|
||||
|
||||
function func2() external returns (uint) {
|
||||
// msg.sender = alice
|
||||
emit Log(msg.sender, "func2", 2);
|
||||
return 111;
|
||||
}
|
||||
|
||||
mapping(address => uint) public balanceOf;
|
||||
|
||||
// WARNING: unsafe code when used in combination with multi-delegatecall
|
||||
// user can mint multiple times for the price of msg.value
|
||||
function mint() external payable {
|
||||
balanceOf[msg.sender] += msg.value;
|
||||
}
|
||||
}
|
||||
|
||||
contract Helper {
|
||||
function getFunc1Data(uint x, uint y) external pure returns (bytes memory) {
|
||||
return abi.encodeWithSelector(TestMultiDelegatecall.func1.selector, x, y);
|
||||
}
|
||||
|
||||
function getFunc2Data() external pure returns (bytes memory) {
|
||||
return abi.encodeWithSelector(TestMultiDelegatecall.func2.selector);
|
||||
}
|
||||
|
||||
function getMintData() external pure returns (bytes memory) {
|
||||
return abi.encodeWithSelector(TestMultiDelegatecall.mint.selector);
|
||||
}
|
||||
}
|
42
advanced_knowledge/calldata/README.md
Normal file
42
advanced_knowledge/calldata/README.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
## calldata
|
||||
|
||||
<br>
|
||||
|
||||
* calldata is the encoded parameter(s) sent on functions by smart contracts to the evm (for example, through `abi.econde()`,`abi.econcdeWithSelector() for a particular interfaced function, or `abi.encodePacked()` for efficient dynamic variables).
|
||||
|
||||
* **function signature**: `selector()` generates the 4-bytes representing the method in the interface: this is how the evm knows which function is being interacted. function signatures are defined as the first four bytes of the Keccak hash of the canonical representation of the function signature.
|
||||
|
||||
* each piece of calldata is 32 bytes long (64 chars), where 20 hex == 32-bytes
|
||||
|
||||
* types:
|
||||
- static variables: the encoded representation of `uint`, `address`, `bool`, `bytes`, `tuples`
|
||||
- dynamics variables: non-fixed-size types: `bytes`, `string`, dynamic and fixed arrays `<T>[]`
|
||||
|
||||
* opcodes are `1 byte` in length, leading to `256` different possible opcodes. the EVM only uses `140` opcodes.
|
||||
|
||||
<br>
|
||||
|
||||
----
|
||||
|
||||
### decompilers
|
||||
|
||||
<br>
|
||||
|
||||
* **[dedaub](https://library.dedaub.com/)**
|
||||
* **[ethervm.io](https://ethervm.io/decompile)**
|
||||
* **[panoramix](https://github.com/eveem-org/panoramix)**
|
||||
* **[froundry's cast with --debug](https://book.getfoundry.sh/cast/index.html)**
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
---
|
||||
|
||||
### resources
|
||||
|
||||
<br>
|
||||
|
||||
* **[mev memoirs in the arena part 1, by noxx](https://noxx.substack.com/p/mev-memoirs-into-the-arena-chapter?s=r)**
|
||||
* **[mev memoirs in the arena part 2, by noxx](https://noxx.substack.com/p/mev-memoirs-into-the-arena-chapter-3e9)**
|
||||
* **[ethereum tx enconding and legacy encoding](https://hoangtrinhj.com/articles/ethereum-transaction-encoding)**
|
92
advanced_knowledge/calldata/precompute_contract_address.sol
Normal file
92
advanced_knowledge/calldata/precompute_contract_address.sol
Normal file
|
@ -0,0 +1,92 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
// Contract address can be precomputed, before the contract is deployed, using create2
|
||||
|
||||
contract Factory {
|
||||
// Returns the address of the newly deployed contract
|
||||
function deploy(
|
||||
address _owner,
|
||||
uint _foo,
|
||||
bytes32 _salt
|
||||
) public payable returns (address) {
|
||||
// This syntax is a newer way to invoke create2 without assembly, you just need to pass salt
|
||||
// https://docs.soliditylang.org/en/latest/control-structures.html#salted-contract-creations-create2
|
||||
return address(new TestContract{salt: _salt}(_owner, _foo));
|
||||
}
|
||||
}
|
||||
|
||||
// This is the older way of doing it using assembly
|
||||
contract FactoryAssembly {
|
||||
event Deployed(address addr, uint salt);
|
||||
|
||||
// 1. Get bytecode of contract to be deployed
|
||||
// NOTE: _owner and _foo are arguments of the TestContract's constructor
|
||||
function getBytecode(address _owner, uint _foo) public pure returns (bytes memory) {
|
||||
bytes memory bytecode = type(TestContract).creationCode;
|
||||
|
||||
return abi.encodePacked(bytecode, abi.encode(_owner, _foo));
|
||||
}
|
||||
|
||||
// 2. Compute the address of the contract to be deployed
|
||||
// NOTE: _salt is a random number used to create an address
|
||||
function getAddress(
|
||||
bytes memory bytecode,
|
||||
uint _salt
|
||||
) public view returns (address) {
|
||||
bytes32 hash = keccak256(
|
||||
abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode))
|
||||
);
|
||||
|
||||
// NOTE: cast last 20 bytes of hash to address
|
||||
return address(uint160(uint(hash)));
|
||||
}
|
||||
|
||||
// 3. Deploy the contract
|
||||
// NOTE:
|
||||
// Check the event log Deployed which contains the address of the deployed TestContract.
|
||||
// The address in the log should equal the address computed from above.
|
||||
function deploy(bytes memory bytecode, uint _salt) public payable {
|
||||
address addr;
|
||||
|
||||
/*
|
||||
NOTE: How to call create2
|
||||
|
||||
create2(v, p, n, s)
|
||||
create new contract with code at memory p to p + n
|
||||
and send v wei
|
||||
and return the new address
|
||||
where new address = first 20 bytes of keccak256(0xff + address(this) + s + keccak256(mem[p…(p+n)))
|
||||
s = big-endian 256-bit value
|
||||
*/
|
||||
assembly {
|
||||
addr := create2(
|
||||
callvalue(), // wei sent with current call
|
||||
// Actual code starts after skipping the first 32 bytes
|
||||
add(bytecode, 0x20),
|
||||
mload(bytecode), // Load the size of code contained in the first 32 bytes
|
||||
_salt // Salt from function arguments
|
||||
)
|
||||
|
||||
if iszero(extcodesize(addr)) {
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
emit Deployed(addr, _salt);
|
||||
}
|
||||
}
|
||||
|
||||
contract TestContract {
|
||||
address public owner;
|
||||
uint public foo;
|
||||
|
||||
constructor(address _owner, uint _foo) payable {
|
||||
owner = _owner;
|
||||
foo = _foo;
|
||||
}
|
||||
|
||||
function getBalance() public view returns (uint) {
|
||||
return address(this).balance;
|
||||
}
|
||||
}
|
22
advanced_knowledge/events/README.md
Normal file
22
advanced_knowledge/events/README.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
## events, tx, topics
|
||||
|
||||
<br>
|
||||
|
||||
* solidity provides two types of events:
|
||||
- anonymous: 4 topics may be indexed, and there is not signature hash (no filter)
|
||||
- non-anonymous (default): up to 3 topics may be indexed, since the first topic is reserved to the event signature (filter)
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
---
|
||||
|
||||
### ethereum transactions
|
||||
|
||||
<br>
|
||||
|
||||
<img width="570" src="https://user-images.githubusercontent.com/126520850/227066769-507080bb-071a-45a7-b385-82b502a963a7.png">
|
||||
|
||||
|
||||
<br>
|
||||
|
5
advanced_knowledge/merkle_trees/README.md
Normal file
5
advanced_knowledge/merkle_trees/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
## a place to dump merkle trees examples
|
||||
|
||||
<br>
|
||||
|
||||
* disclaimer: these boilerplates might not be my code, but rather well-known references.
|
81
advanced_knowledge/merkle_trees/tree_example1.sol
Normal file
81
advanced_knowledge/merkle_trees/tree_example1.sol
Normal file
|
@ -0,0 +1,81 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
// Merkle tree allows you to cryptographically prove that an element is contained
|
||||
// in a set without revealing the entire set.
|
||||
|
||||
contract MerkleProof {
|
||||
function verify(
|
||||
bytes32[] memory proof,
|
||||
bytes32 root,
|
||||
bytes32 leaf,
|
||||
uint index
|
||||
) public pure returns (bool) {
|
||||
bytes32 hash = leaf;
|
||||
|
||||
for (uint i = 0; i < proof.length; i++) {
|
||||
bytes32 proofElement = proof[i];
|
||||
|
||||
if (index % 2 == 0) {
|
||||
hash = keccak256(abi.encodePacked(hash, proofElement));
|
||||
} else {
|
||||
hash = keccak256(abi.encodePacked(proofElement, hash));
|
||||
}
|
||||
|
||||
index = index / 2;
|
||||
}
|
||||
|
||||
return hash == root;
|
||||
}
|
||||
}
|
||||
|
||||
contract TestMerkleProof is MerkleProof {
|
||||
bytes32[] public hashes;
|
||||
|
||||
constructor() {
|
||||
string[4] memory transactions = [
|
||||
"alice -> bob",
|
||||
"bob -> dave",
|
||||
"carol -> alice",
|
||||
"dave -> bob"
|
||||
];
|
||||
|
||||
for (uint i = 0; i < transactions.length; i++) {
|
||||
hashes.push(keccak256(abi.encodePacked(transactions[i])));
|
||||
}
|
||||
|
||||
uint n = transactions.length;
|
||||
uint offset = 0;
|
||||
|
||||
while (n > 0) {
|
||||
for (uint i = 0; i < n - 1; i += 2) {
|
||||
hashes.push(
|
||||
keccak256(
|
||||
abi.encodePacked(hashes[offset + i], hashes[offset + i + 1])
|
||||
)
|
||||
);
|
||||
}
|
||||
offset += n;
|
||||
n = n / 2;
|
||||
}
|
||||
}
|
||||
|
||||
function getRoot() public view returns (bytes32) {
|
||||
return hashes[hashes.length - 1];
|
||||
}
|
||||
|
||||
/* verify
|
||||
3rd leaf
|
||||
0xdca3326ad7e8121bf9cf9c12333e6b2271abe823ec9edfe42f813b1e768fa57b
|
||||
|
||||
root
|
||||
0xcc086fcc038189b4641db2cc4f1de3bb132aefbd65d510d817591550937818c7
|
||||
|
||||
index
|
||||
2
|
||||
|
||||
proof
|
||||
0x8da9e1c820f9dbd1589fd6585872bc1063588625729e7ab0797cfc63a00bd950
|
||||
0x995788ffc103b987ad50f5e5707fd094419eb12d9552cc423bd0cd86a3861433
|
||||
*/
|
||||
}
|
5
advanced_knowledge/proxies/README.md
Normal file
5
advanced_knowledge/proxies/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
## a place to dump proxy examples
|
||||
|
||||
<br>
|
||||
|
||||
* disclaimer: these boilerplates might not be my code, but rather well-known references.
|
64
advanced_knowledge/proxies/deploy.sol
Normal file
64
advanced_knowledge/proxies/deploy.sol
Normal file
|
@ -0,0 +1,64 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
contract Proxy {
|
||||
event Deploy(address);
|
||||
|
||||
receive() external payable {}
|
||||
|
||||
function deploy(bytes memory _code) external payable returns (address addr) {
|
||||
assembly {
|
||||
// create(v, p, n)
|
||||
// v = amount of ETH to send
|
||||
// p = pointer in memory to start of code
|
||||
// n = size of code
|
||||
addr := create(callvalue(), add(_code, 0x20), mload(_code))
|
||||
}
|
||||
// return address 0 on error
|
||||
require(addr != address(0), "deploy failed");
|
||||
|
||||
emit Deploy(addr);
|
||||
}
|
||||
|
||||
function execute(address _target, bytes memory _data) external payable {
|
||||
(bool success, ) = _target.call{value: msg.value}(_data);
|
||||
require(success, "failed");
|
||||
}
|
||||
}
|
||||
|
||||
contract TestContract1 {
|
||||
address public owner = msg.sender;
|
||||
|
||||
function setOwner(address _owner) public {
|
||||
require(msg.sender == owner, "not owner");
|
||||
owner = _owner;
|
||||
}
|
||||
}
|
||||
|
||||
contract TestContract2 {
|
||||
address public owner = msg.sender;
|
||||
uint public value = msg.value;
|
||||
uint public x;
|
||||
uint public y;
|
||||
|
||||
constructor(uint _x, uint _y) payable {
|
||||
x = _x;
|
||||
y = _y;
|
||||
}
|
||||
}
|
||||
|
||||
contract Helper {
|
||||
function getBytecode1() external pure returns (bytes memory) {
|
||||
bytes memory bytecode = type(TestContract1).creationCode;
|
||||
return bytecode;
|
||||
}
|
||||
|
||||
function getBytecode2(uint _x, uint _y) external pure returns (bytes memory) {
|
||||
bytes memory bytecode = type(TestContract2).creationCode;
|
||||
return abi.encodePacked(bytecode, abi.encode(_x, _y));
|
||||
}
|
||||
|
||||
function getCalldata(address _owner) external pure returns (bytes memory) {
|
||||
return abi.encodeWithSignature("setOwner(address)", _owner);
|
||||
}
|
||||
}
|
70
advanced_knowledge/proxies/minimum_proxy.sol
Normal file
70
advanced_knowledge/proxies/minimum_proxy.sol
Normal file
|
@ -0,0 +1,70 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
|
||||
|
||||
contract MinimalProxy {
|
||||
function clone(address target) external returns (address result) {
|
||||
// convert address to 20 bytes
|
||||
bytes20 targetBytes = bytes20(target);
|
||||
|
||||
// actual code //
|
||||
// 3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3
|
||||
|
||||
// creation code //
|
||||
// copy runtime code into memory and return it
|
||||
// 3d602d80600a3d3981f3
|
||||
|
||||
// runtime code //
|
||||
// code to delegatecall to address
|
||||
// 363d3d373d3d3d363d73 address 5af43d82803e903d91602b57fd5bf3
|
||||
|
||||
assembly {
|
||||
/*
|
||||
reads the 32 bytes of memory starting at pointer stored in 0x40
|
||||
|
||||
In solidity, the 0x40 slot in memory is special: it contains the "free memory pointer"
|
||||
which points to the end of the currently allocated memory.
|
||||
*/
|
||||
let clone := mload(0x40)
|
||||
// store 32 bytes to memory starting at "clone"
|
||||
mstore(
|
||||
clone,
|
||||
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
|
||||
)
|
||||
|
||||
/*
|
||||
| 20 bytes |
|
||||
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
|
||||
^
|
||||
pointer
|
||||
*/
|
||||
// store 32 bytes to memory starting at "clone" + 20 bytes
|
||||
// 0x14 = 20
|
||||
mstore(add(clone, 0x14), targetBytes)
|
||||
|
||||
/*
|
||||
| 20 bytes | 20 bytes |
|
||||
0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe
|
||||
^
|
||||
pointer
|
||||
*/
|
||||
// store 32 bytes to memory starting at "clone" + 40 bytes
|
||||
// 0x28 = 40
|
||||
mstore(
|
||||
add(clone, 0x28),
|
||||
0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
|
||||
)
|
||||
|
||||
/*
|
||||
| 20 bytes | 20 bytes | 15 bytes |
|
||||
0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3
|
||||
*/
|
||||
// create new contract
|
||||
// send 0 Ether
|
||||
// code starts at pointer stored in "clone"
|
||||
// code size 0x37 (55 bytes)
|
||||
result := create(0, clone, 0x37)
|
||||
}
|
||||
}
|
||||
}
|
236
advanced_knowledge/proxies/upgradeable_proxy.sol
Normal file
236
advanced_knowledge/proxies/upgradeable_proxy.sol
Normal file
|
@ -0,0 +1,236 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
// Transparent upgradeable proxy pattern
|
||||
|
||||
contract CounterV1 {
|
||||
uint public count;
|
||||
|
||||
function inc() external {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
contract CounterV2 {
|
||||
uint public count;
|
||||
|
||||
function inc() external {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
function dec() external {
|
||||
count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
contract BuggyProxy {
|
||||
address public implementation;
|
||||
address public admin;
|
||||
|
||||
constructor() {
|
||||
admin = msg.sender;
|
||||
}
|
||||
|
||||
function _delegate() private {
|
||||
(bool ok, ) = implementation.delegatecall(msg.data);
|
||||
require(ok, "delegatecall failed");
|
||||
}
|
||||
|
||||
fallback() external payable {
|
||||
_delegate();
|
||||
}
|
||||
|
||||
receive() external payable {
|
||||
_delegate();
|
||||
}
|
||||
|
||||
function upgradeTo(address _implementation) external {
|
||||
require(msg.sender == admin, "not authorized");
|
||||
implementation = _implementation;
|
||||
}
|
||||
}
|
||||
|
||||
contract Dev {
|
||||
function selectors() external view returns (bytes4, bytes4, bytes4) {
|
||||
return (
|
||||
Proxy.admin.selector,
|
||||
Proxy.implementation.selector,
|
||||
Proxy.upgradeTo.selector
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
contract Proxy {
|
||||
// All functions / variables should be private, forward all calls to fallback
|
||||
|
||||
// -1 for unknown preimage
|
||||
// 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
|
||||
bytes32 private constant IMPLEMENTATION_SLOT =
|
||||
bytes32(uint(keccak256("eip1967.proxy.implementation")) - 1);
|
||||
// 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
|
||||
bytes32 private constant ADMIN_SLOT =
|
||||
bytes32(uint(keccak256("eip1967.proxy.admin")) - 1);
|
||||
|
||||
constructor() {
|
||||
_setAdmin(msg.sender);
|
||||
}
|
||||
|
||||
modifier ifAdmin() {
|
||||
if (msg.sender == _getAdmin()) {
|
||||
_;
|
||||
} else {
|
||||
_fallback();
|
||||
}
|
||||
}
|
||||
|
||||
function _getAdmin() private view returns (address) {
|
||||
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
|
||||
}
|
||||
|
||||
function _setAdmin(address _admin) private {
|
||||
require(_admin != address(0), "admin = zero address");
|
||||
StorageSlot.getAddressSlot(ADMIN_SLOT).value = _admin;
|
||||
}
|
||||
|
||||
function _getImplementation() private view returns (address) {
|
||||
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
|
||||
}
|
||||
|
||||
function _setImplementation(address _implementation) private {
|
||||
require(_implementation.code.length > 0, "implementation is not contract");
|
||||
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = _implementation;
|
||||
}
|
||||
|
||||
// Admin interface //
|
||||
function changeAdmin(address _admin) external ifAdmin {
|
||||
_setAdmin(_admin);
|
||||
}
|
||||
|
||||
// 0x3659cfe6
|
||||
function upgradeTo(address _implementation) external ifAdmin {
|
||||
_setImplementation(_implementation);
|
||||
}
|
||||
|
||||
// 0xf851a440
|
||||
function admin() external ifAdmin returns (address) {
|
||||
return _getAdmin();
|
||||
}
|
||||
|
||||
// 0x5c60da1b
|
||||
function implementation() external ifAdmin returns (address) {
|
||||
return _getImplementation();
|
||||
}
|
||||
|
||||
// User interface //
|
||||
function _delegate(address _implementation) internal virtual {
|
||||
assembly {
|
||||
// Copy msg.data. We take full control of memory in this inline assembly
|
||||
// block because it will not return to Solidity code. We overwrite the
|
||||
// Solidity scratch pad at memory position 0.
|
||||
|
||||
// calldatacopy(t, f, s) - copy s bytes from calldata at position f to mem at position t
|
||||
// calldatasize() - size of call data in bytes
|
||||
calldatacopy(0, 0, calldatasize())
|
||||
|
||||
// Call the implementation.
|
||||
// out and outsize are 0 because we don't know the size yet.
|
||||
|
||||
// delegatecall(g, a, in, insize, out, outsize) -
|
||||
// - call contract at address a
|
||||
// - with input mem[in…(in+insize))
|
||||
// - providing g gas
|
||||
// - and output area mem[out…(out+outsize))
|
||||
// - returning 0 on error (eg. out of gas) and 1 on success
|
||||
let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
|
||||
|
||||
// Copy the returned data.
|
||||
// returndatacopy(t, f, s) - copy s bytes from returndata at position f to mem at position t
|
||||
// returndatasize() - size of the last returndata
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
|
||||
switch result
|
||||
// delegatecall returns 0 on error.
|
||||
case 0 {
|
||||
// revert(p, s) - end execution, revert state changes, return data mem[p…(p+s))
|
||||
revert(0, returndatasize())
|
||||
}
|
||||
default {
|
||||
// return(p, s) - end execution, return data mem[p…(p+s))
|
||||
return(0, returndatasize())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _fallback() private {
|
||||
_delegate(_getImplementation());
|
||||
}
|
||||
|
||||
fallback() external payable {
|
||||
_fallback();
|
||||
}
|
||||
|
||||
receive() external payable {
|
||||
_fallback();
|
||||
}
|
||||
}
|
||||
|
||||
contract ProxyAdmin {
|
||||
address public owner;
|
||||
|
||||
constructor() {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
modifier onlyOwner() {
|
||||
require(msg.sender == owner, "not owner");
|
||||
_;
|
||||
}
|
||||
|
||||
function getProxyAdmin(address proxy) external view returns (address) {
|
||||
(bool ok, bytes memory res) = proxy.staticcall(abi.encodeCall(Proxy.admin, ()));
|
||||
require(ok, "call failed");
|
||||
return abi.decode(res, (address));
|
||||
}
|
||||
|
||||
function getProxyImplementation(address proxy) external view returns (address) {
|
||||
(bool ok, bytes memory res) = proxy.staticcall(
|
||||
abi.encodeCall(Proxy.implementation, ())
|
||||
);
|
||||
require(ok, "call failed");
|
||||
return abi.decode(res, (address));
|
||||
}
|
||||
|
||||
function changeProxyAdmin(address payable proxy, address admin) external onlyOwner {
|
||||
Proxy(proxy).changeAdmin(admin);
|
||||
}
|
||||
|
||||
function upgrade(address payable proxy, address implementation) external onlyOwner {
|
||||
Proxy(proxy).upgradeTo(implementation);
|
||||
}
|
||||
}
|
||||
|
||||
library StorageSlot {
|
||||
struct AddressSlot {
|
||||
address value;
|
||||
}
|
||||
|
||||
function getAddressSlot(
|
||||
bytes32 slot
|
||||
) internal pure returns (AddressSlot storage r) {
|
||||
assembly {
|
||||
r.slot := slot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract TestSlot {
|
||||
bytes32 public constant slot = keccak256("TEST_SLOT");
|
||||
|
||||
function getSlot() external view returns (address) {
|
||||
return StorageSlot.getAddressSlot(slot).value;
|
||||
}
|
||||
|
||||
function writeSlot(address _addr) external {
|
||||
StorageSlot.getAddressSlot(slot).value = _addr;
|
||||
}
|
||||
}
|
292
advanced_knowledge/saving_gas/README.md
Normal file
292
advanced_knowledge/saving_gas/README.md
Normal file
|
@ -0,0 +1,292 @@
|
|||
## gas optimization
|
||||
|
||||
<br>
|
||||
|
||||
- gas is the cost for on-chain computation and storage. examples:
|
||||
- addition costs `3` gas, `keccak-256` costs `30 gas + 6 gas` for each `256 bits` of data being hashed.
|
||||
- sending a transaction costs `21,000 gas` (intrinsic gas).
|
||||
- creating a contract costs `32000 gas`.
|
||||
- each **calldata** byte costs gas (gas per byte equal to `0`, and `16 gas` for the others), the larger the size of the transaction data, the higher the gas fees.
|
||||
- each opcode has a [specific fixed cost to be paid upon execution](https://www.evm.codes/?fork=arrowGlacier).
|
||||
- calculate gas for your code online at [remix](https://remix.ethereum.org/).
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
----
|
||||
|
||||
|
||||
### general tricks to save gas
|
||||
|
||||
<br>
|
||||
|
||||
* replace `memory` with `calldata`
|
||||
* load state variable to memory
|
||||
* replace for loop `i++` with `++i`
|
||||
* caching array elements
|
||||
* short circuits
|
||||
* brute force hashes of function names to find those that start `0000`, so this can save around `50 gas`.
|
||||
* if you don’t need a variable anymore, you should delete it using the delete keyword provided by solidity or by setting it to its default value.
|
||||
* avoid calls to other contracts.
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<img width="300" src="https://user-images.githubusercontent.com/1130416/214452718-b051caed-49e4-45fb-b955-976d20e97cbd.png">
|
||||
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
```
|
||||
// gas golf
|
||||
contract GasGolf {
|
||||
// start - 50908 gas
|
||||
// use calldata - 49163 gas
|
||||
// load state variables to memory - 48952 gas
|
||||
// short circuit - 48634 gas
|
||||
// loop increments - 48244 gas
|
||||
// cache array length - 48209 gas
|
||||
// load array elements to memory - 48047 gas
|
||||
// uncheck i overflow/underflow - 47309 gas
|
||||
|
||||
uint public total;
|
||||
|
||||
// start - not gas optimized
|
||||
// function sumIfEvenAndLessThan99(uint[] memory nums) external {
|
||||
// for (uint i = 0; i < nums.length; i += 1) {
|
||||
// bool isEven = nums[i] % 2 == 0;
|
||||
// bool isLessThan99 = nums[i] < 99;
|
||||
// if (isEven && isLessThan99) {
|
||||
// total += nums[i];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// gas optimized
|
||||
// [1, 2, 3, 4, 5, 100]
|
||||
function sumIfEvenAndLessThan99(uint[] calldata nums) external {
|
||||
uint _total = total;
|
||||
uint len = nums.length;
|
||||
|
||||
for (uint i = 0; i < len; ) {
|
||||
uint num = nums[i];
|
||||
if (num % 2 == 0 && num < 99) {
|
||||
_total += num;
|
||||
}
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
total = _total;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### pack variables
|
||||
|
||||
<br>
|
||||
|
||||
* this code is an example of poor code and will consume 3 storage slot:
|
||||
|
||||
```
|
||||
uint8 numberOne;
|
||||
uint256 bigNumber;
|
||||
uint8 numberTwo;
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
* a much more efficient way to do this in solidity will be:
|
||||
|
||||
```
|
||||
uint8 numberOne;
|
||||
uint8 numberTwo;
|
||||
uint256 bigNumber;
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
|
||||
### constant vs. immutable variables
|
||||
|
||||
<br>
|
||||
|
||||
* constant values can sometimes be cheaper than immutable values:
|
||||
- for a constant variable, the expression assigned to it is copied to all the places where it is accessed and also re-evaluated each time, allowing local optimizations.
|
||||
- immutable variables are evaluated once at construction time and their value is copied to all the places in the code where they are accessed. For these values, 32 bytes are reserved, even if they would fit in fewer bytes.
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### mappings are cheaper than arrays
|
||||
|
||||
<br>
|
||||
|
||||
- avoid dynamically sized arrays
|
||||
- an array is not stored sequentially in memory but as a mapping.
|
||||
- you can pack Arrays but not Mappings.
|
||||
- it’s cheaper to use arrays if you are using smaller elements like `uint8` which can be packed together.
|
||||
- you can’t get the length of a mapping or parse through all its elements, so depending on your use case, you might be forced to use an Array even though it might cost you more gas.
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### use bytes32 rather than string/bytes
|
||||
|
||||
<br>
|
||||
|
||||
- if you can fit your data in 32 bytes, then you should use `bytes32` datatype rather than bytes or strings as it is much cheaper in solidity.
|
||||
- any fixed size variable in solidity is cheaper than variable size.
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### modifiers
|
||||
|
||||
<br>
|
||||
|
||||
- for all the public functions, the input parameters are copied to memory automatically, and it costs gas.
|
||||
- if your function is only called externally, then you should explicitly mark it as external.
|
||||
- external function’s parameters are not copied into memory but are read from `calldata` directly.
|
||||
- internal and private are both cheaper than public and external when called from inside the contract in some cases.
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### no need to initialize variables with default values
|
||||
|
||||
<br>
|
||||
|
||||
- if a variable is not set/initialized, it is assumed to have the default value (0, false, 0x0 etc depending on the data type). If you explicitly initialize it with its default value, you are just wasting gas.
|
||||
|
||||
```
|
||||
uint256 hello = 0; //bad, expensive
|
||||
uint256 world; //good, cheap
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### make use of single line swaps
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
- this is space-efficient:
|
||||
|
||||
```
|
||||
(hello, world) = (world, hello)
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### negative gas costs
|
||||
|
||||
<br>
|
||||
|
||||
- deleting a contract (SELFDESTRUCT) is worth a refund of 24,000 gas.
|
||||
- changing a storage address from a nonzero value to zero (SSTORE[x] = 0) is worth a refund of 15,000 gas.
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### [i ++](https://twitter.com/high_byte/status/1647080662094995457?s=20)
|
||||
|
||||
<br>
|
||||
|
||||
* instead of i++ you should write the ++ above the i.
|
||||
|
||||
```
|
||||
while (true)
|
||||
uint256 i = 0;
|
||||
++
|
||||
i
|
||||
;
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
----
|
||||
|
||||
|
||||
### unchecked math
|
||||
|
||||
<br>
|
||||
|
||||
* overflow and underflow of numbers in solidity 0.8 throw an error. this can be disabled with `unchecked`.
|
||||
* disabling overflow / underflow check saves gas.
|
||||
|
||||
<br>
|
||||
|
||||
```
|
||||
contract UncheckedMath {
|
||||
function add(uint x, uint y) external pure returns (uint) {
|
||||
// 22291 gas
|
||||
// return x + y;
|
||||
|
||||
// 22103 gas
|
||||
unchecked {
|
||||
return x + y;
|
||||
}
|
||||
}
|
||||
|
||||
function sub(uint x, uint y) external pure returns (uint) {
|
||||
// 22329 gas
|
||||
// return x - y;
|
||||
|
||||
// 22147 gas
|
||||
unchecked {
|
||||
return x - y;
|
||||
}
|
||||
}
|
||||
|
||||
function sumOfCubes(uint x, uint y) external pure returns (uint) {
|
||||
// Wrap complex math logic inside unchecked
|
||||
unchecked {
|
||||
uint x3 = x * x * x;
|
||||
uint y3 = y * y * y;
|
||||
|
||||
return x3 + y3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
### resources
|
||||
|
||||
<br>
|
||||
|
||||
* **[truffle contract size](https://github.com/IoBuilders/truffle-contract-size)**
|
||||
* **[foundry book on gas](https://book.getfoundry.sh/forge/gas-reports)**
|
||||
* **[solidity gas optimizations](https://mirror.xyz/haruxe.eth/DW5verFv8KsYOBC0SxqWORYry17kPdeS94JqOVkgxAA)**
|
||||
* **[hardhat on gas optimization](https://medium.com/@thelasthash/%EF%B8%8F-gas-optimization-with-hardhat-1e553eaea311)**
|
||||
* **[resources for gas optimization](https://github.com/kadenzipfel/gas-optimizations)**
|
||||
* **[awesome solidity gas optimization](https://github.com/iskdrews/awesome-solidity-gas-optimization)**
|
||||
* **[mev-toolkit resources](https://github.com/go-outside-labs/mev-toolkit/tree/main/MEV_searchers/code_resources/gas_optimization)**
|
||||
* **[how gas optimization can streamline smart contracts](https://medium.com/@ayomilk1/maximizing-efficiency-how-gas-optimization-can-streamline-your-smart-contracts-4bafcc6bf321)**
|
||||
* **[math, solidity & gas optimizations | part 1/3](https://officercia.mirror.xyz/vtVVxbV35ETiBGxm-IpcFPcsK2_ZkL7vgiiGUkeSsP0)**
|
||||
|
65
advanced_knowledge/saving_gas/gasless_token_transfer.sol
Normal file
65
advanced_knowledge/saving_gas/gasless_token_transfer.sol
Normal file
|
@ -0,0 +1,65 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.18;
|
||||
|
||||
// gaslless erc20 token transfer with meta transaction
|
||||
|
||||
interface IERC20Permit {
|
||||
function totalSupply() external view returns (uint256);
|
||||
|
||||
function balanceOf(address account) external view returns (uint256);
|
||||
|
||||
function transfer(address recipient, uint256 amount) external returns (bool);
|
||||
|
||||
function allowance(address owner, address spender) external view returns (uint256);
|
||||
|
||||
function approve(address spender, uint256 amount) external returns (bool);
|
||||
|
||||
function transferFrom(
|
||||
address sender,
|
||||
address recipient,
|
||||
uint256 amount
|
||||
) external returns (bool);
|
||||
|
||||
function permit(
|
||||
address owner,
|
||||
address spender,
|
||||
uint256 value,
|
||||
uint256 deadline,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) external;
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||
}
|
||||
|
||||
contract GaslessTokenTransfer {
|
||||
function send(
|
||||
address token,
|
||||
address sender,
|
||||
address receiver,
|
||||
uint256 amount,
|
||||
uint256 fee,
|
||||
uint256 deadline,
|
||||
// Permit signature
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) external {
|
||||
// Permit
|
||||
IERC20Permit(token).permit(
|
||||
sender,
|
||||
address(this),
|
||||
amount + fee,
|
||||
deadline,
|
||||
v,
|
||||
r,
|
||||
s
|
||||
);
|
||||
// Send amount to receiver
|
||||
IERC20Permit(token).transferFrom(sender, receiver, amount);
|
||||
// Take fee - send fee to msg.sender
|
||||
IERC20Permit(token).transferFrom(sender, msg.sender, fee);
|
||||
}
|
||||
}
|
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