[TIL] 2023-06-27 DAY65
GIT 주소
https://github.com/dowoo303/SOLIDITY
https://github.com/dowoo303/hardhat
오늘 배운것들
- CPMM 실습
- event emit
- ERC20 토큰으로 NFT 민팅해보기(with react)
각종 팁
- 가스비 관련
1) 단일매핑 2번보다는 이중매핑이 싸다
2) 최대한 상태변수 쓰지말기
3) 길이가 정해진 배열 사용하기(동적 어레이와 정적 어레이의 가스비 차이는 꽤 난다)
4) modifier은 유무에 따른 가스비 차이는 거의 없음
회고
- react에서 컨트랙트와 함수명 일치하는지 확인하기
x(a토큰갯수) x y(b토큰갯수) = k 일 때 코인 거래가 일어난다고 k값은 변화하지 않음 -> liquidity pool 변동이 일어날 때만 변화가 일어난다.
msg.sender는 컨트랙트가 될 수도 있지만 tx.origin은 무조건 사용자이다.
@openzeppelin/contracts/token/ERC20/ERC20.sol 형태로 받아온다면 npm사이트에서 코드를 봐야함.
https://www.npmjs.com/package/@openzeppelin/contracts?activeTab=code
transferFrom는 _spendAllowance가 있기 때문에 approve가 필요하다. -> cpmm 주소를 가지고 a,b토큰 컨트랙트로 가서 approve -> A토큰 주소를 가지고 가서 cpmm에서 풀 제공
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
contract Token_A is ERC20("A_token", "A") {
constructor(uint a) {
_mint(msg.sender, a);
}
function decimals() public pure override returns(uint8) {
return 0;
}
}
contract Token_B is ERC20("B_token", "B") {
constructor(uint a) {
_mint(msg.sender, a);
}
function decimals() public pure override returns(uint8) {
return 0;
}
}
contract CPMM is ERC20("ABLP_Token", "ABLP") {
address owner;
ERC20 public token_a;
ERC20 public token_b;
constructor(address _a, address _b) {
owner = msg.sender;
token_a = ERC20(_a);
token_b = ERC20(_b);
}
function burn(uint _amount) public {
_burn(msg.sender, _amount);
}
// x(a토큰의 수량)*y(b토큰의 수량)=k -> CPMM
//uint public k;
modifier isitAorB(address _token) {
require(_token == address(token_a) || _token == address(token_b));
_;
}
// 토큰 가격 결정을 위하여 컨트랙트가 들고 있는 갯수 구하기 -> 토큰 A, B의 컨트랙트에서 가져오기
function balanceOfAB() public view returns(uint, uint) {
return (token_a.balanceOf(address(this)), token_b.balanceOf(address(this)));
}
// 유동성 공급
// 초기 유동성 공급
function initialSupply() public {
require(owner == msg.sender);
token_a.transferFrom(tx.origin, address(this), 100);
token_b.transferFrom(tx.origin, address(this), 300);
_mint(msg.sender, 50);
}
// A토큰만 공급한다고 가정
function provideLiquidity(address _token, uint _a) public isitAorB(_token) {
(uint bal_a, uint bal_b) = balanceOfAB();
uint mint_lp;
if(_token == address(token_a)) {
// A토큰 기준 -> B토큰 = 사용자가 바꾸려는 A토큰 갯수 * B토큰 갯수 / A토큰 갯수
uint _b = _a * bal_b / bal_a;
token_a.transferFrom(tx.origin, address(this), _a);
token_b.transferFrom(tx.origin, address(this), _b);
// lp 토큰 지급 -> 사용자가 넣은 A토큰 갯수 * 현재 pool의 k갯수(LP토큰) / 현재 이 pool의 A토큰 갯수
mint_lp = _a * totalSupply() / bal_a;
} else {
uint _b = _a * bal_a / bal_b;
token_a.transferFrom(tx.origin, address(this), _b);
token_b.transferFrom(tx.origin, address(this), _a);
mint_lp = _a * totalSupply() / bal_b;
}
_mint(msg.sender, mint_lp);
// k = (bal_a + _a) * (bal_b + b);
}
// 유동성 제거
function withdrawLiquidity(uint _a) public {
// _a : 사용자가 빼고 싶은 LP토큰의 양
require(balanceOf(msg.sender) >= _a, "not enough");
(uint bal_a, uint bal_b) = balanceOfAB();
uint a_out = _a * bal_a / totalSupply();
uint b_out = _a * bal_b / totalSupply();
burn(_a); // LP 토큰 소각
token_a.transfer(msg.sender, a_out);
token_b.transfer(msg.sender, b_out);
// k = (bal_a - _a) * (bal_b - _b);
}
// 거래
function swapToken(address _token, uint _amount) public isitAorB(_token) {
(uint bal_a, uint bal_b) = balanceOfAB();
uint k = bal_a * bal_b;
if(_token == address(token_a)) {
// out = b-b` -> b` = a*b/(a+a`)=k/(a+a`)
uint out = bal_b - k / (bal_a + _amount);
token_a.transferFrom(tx.origin, address(this), _amount);
token_b.transfer(msg.sender, out);
} else {
uint out = bal_a - k / (bal_b + _amount);
token_b.transferFrom(tx.origin, address(this), _amount);
token_a.transfer(msg.sender, out);
}
}
// 모인 수수료(보상) 지급
function ratio() public view returns(uint) {
uint _ratio = balanceOf(msg.sender) * 100 / totalSupply();
// uint[2] memory _rewards = [fees[0] * ratio / 100, fees[1] * ratio/100];
return _ratio;
}
}
이벤트 emit을 남기는 코드 구성 후 터미널에서 web3를 이용하여 트랜잭션 일으키고 filter로 특정 트랜잭션만 걸러내보기
트랜잭션 터미널(이더리움 테스트넷)
var tx = {from : account.address, to : c_addr, gas : 300000, gasPrice : 300000000, data : contract.methods.event_1(1, 'a').encodeABI()}
var signPromise = web3.eth.accounts.signTransaction(tx, account.privateKey)
signPromise.then((signedTx) => {var sentTx = web3.eth.sendSignedTransaction(signedTx.raw || signedTx.rawTransaction);
sentTx.on("receipt", receipt => {console.log(receipt.logs)})})
web3.utils.sha3('이벤트이름(input값형태1,input값형태2)')
-filter 써보기
var tx = {from : account.address, to : c_addr, gas : 300000, gasPrice : 300000000, data : contract.methods.event_1(2, 'abc').encodeABI()}
var signPromise = web3.eth.accounts.signTransaction(tx, account.privateKey)
signPromise.then((signedTx) => {var sentTx = web3.eth.sendSignedTransaction(signedTx.raw || signedTx.rawTransaction); sentTx.on("receipt", receipt => {console.log(receipt.logs)})})
- 특정값만 조회해보기
async function getEvents() {var a = await contract.getPastEvents('index_1', {filter : {_num : [3,4]}, fromBlock : 0, toBlock : 'latest'}); console.log(a)}
async function getEvents() {var a = await contract.getPastEvents('index_1', {filter : {_num : [3,4]}, fromBlock : 0, toBlock : 'latest'}); console.log(a[0].returnValues)}
-> 맨 뒤 console.log(a[0]) 쪽 건드리기
웹소켓 터미널에서는 위와 컨트랙트 만드는것까지는 같음(웹소켓)
contract.events.index_1(()=>{}).on('data', function(event){console.log(event)})
await contract.getPastEvents('index_1', {fromBlock : 0, toBlock : 'latest'}, (err,events) => {console.log(events);});
-> 이 컨트랙트 내 모든 트랜잭션 읽어옴
await contract.getPastEvents('index_1', {fromBlock : 9247980, toBlock : 'latest'}, (err,events) => {console.log(events);});
-> fromBlock과 toBlock을 변경하여 원하는 기간동안의 정보만 받아 올 수도 있음
- filter 써보기
await contract.getPastEvents('index_1', {filter : {_num : [2]}, fromBlock : 0, toBlock : 'latest'}, (err,events) => {console.log(events);});
-> _num이 2인 것들만 filter해서 표시해줌

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract Indexed {
event index_1(uint indexed _num, string _name);
function event_1(uint _n, string memory _s) public {
emit index_1(_n, _s);
}
}
위 CPMM에서 했던 방식과 유사하다. -> approve도 현재 react에서 구현한 것이 아닌 remix에서 가서 해준 후 NFT 민팅을 진행했다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Changeable is ERC721Enumerable {
string public URI;
ERC20 public token_a;
function setToken_A(address _a) public {
token_a = ERC20(_a);
}
constructor(string memory _uri, address _a) ERC721("nft721", "n") {
URI = _uri;
token_a = ERC20(_a);
}
// ERC721이 받음
function mintNFT(uint _ID) public payable {
require(msg.value == 0.001 ether);
_mint(msg.sender, _ID);
}
// A토큰으로 민팅하기 -> ERC20(token_a)이 받음
function mintNFTwithERC20(uint _ID) public {
token_a.transferFrom(tx.origin, address(this), 1000);
_mint(msg.sender, _ID);
}
function tokenURI(uint _tokenID) public override view returns(string memory) {
return string(abi.encodePacked(URI, '/', Strings.toString(_tokenID), '.json')); // 공통적인 부분 /, 숫자, .json 붙여주기
}
}