[TIL] 2023-07-04 DAY70
GIT 주소
https://github.com/dowoo303/SOLIDITY
https://github.com/dowoo303/hardhat
오늘 배운것들
- 가스비 아끼는법
- memory와 storage
- VRF 활용
각종 팁
- 가스비를 이해하려면 OP코드를 어느정도 이해하는 것이 도움이 된다.
회고
uint 상태변수에 0에서 1로 더할 때가 가장 가스비가 많이 든다.
-> 초기 uint 상태변수 값을 신경써주는 것이 좋음(만약 상태변수가 1로 시작하면 많이 안듦)
-> 지역변수를 활용하여 상태변수에 값을 꽂아주는 것이 제일 저렴하다.
반면 뺄셈에서 상태변수가 1 -> 0으로 갈 때는 가스비가 싸다.
기본적으로 memory보다는 calldata가 더 저렴하다.
calldata는 초기값이 있는게 더 싸지만, memory는 초기값이 없는게 더 싸다
가격 싼 순서: calldata(초기값 존재) -> calldata(초기값 없음) -> memory(초기값 없음) -> clladata(초기값 존재)
visibility에 따른 가스비 차이는 미미하다.
storage를 사용하면 원본을 바꾸면 쳐다보는 애도 변하고, 쳐다보는 애를 바꿔도 원본이 바뀐다.
-> storage 쓰는이유: 쳐다보는 애의 값을 보여주는걸로 원본의 값을 보여주되 변경은 불가하게(private) 만들어쓰기 위함(보안)
가스비 측면에서는 storage를 사용하지않고 그냥 원래 변수 값에 넣는 것이 저렴하다.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract MEMORY_STORAGE {
struct AA {
uint a;
string b;
}
AA public aa;
function setAA(uint _a, string calldata _b) public {
aa.a = _a;
aa.b = _b;
}
function setAA_memory(uint _a, string calldata _b) public pure returns(AA memory){
AA memory _aa;
_aa.a = _a;
_aa.b = _b;
return (_aa);
}
function setAA_memory2(uint _a, string calldata _b) public view returns(AA memory, AA memory){
AA memory _aa = aa;
_aa.a = _a;
_aa.b = _b;
return (_aa, aa);
}
// 위와 다르게 aa 값 또한 변한다
function setAA_storage(uint _a, string calldata _b) public returns(AA memory, AA memory) {
AA storage _aa = aa;
_aa.a = _a;
_aa.b = _b;
return (_aa, aa);
}
// _aa 또한 storage를 사용하면 변한다
function setAA_storage2(uint _a, string calldata _b) public returns(AA memory, AA memory) {
AA storage _aa = aa;
aa.a = _a;
aa.b = _b;
return (_aa, aa);
}
}
홈페이지에서 작업 순서
위 홈페이지에서 LINK 코인을 받기 -> Subscription 생성 -> 컨트랙트 주소 입력 -> 리믹스 코드 실행
코드 실행순서
리믹스에서 requestRandomWords 함수 실행 후 getRequestStatus에 결과값 넣고 함수 실행 후 조금 기다리면 fullfilled가 true로 바뀌고 랜덤 난수가 생성된다.
원리
requestRandomWords으로 난수 생성 후 getRequestStatus에서 확인 및 이 값을 이용하여 필요한 함수에 넣어서 사용(ex)주사위 게임, 99게임 등등)
// SPDX-License-Identifier: MIT
// An example of a consumer contract that relies on a subscription for funding.
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";
contract VRFv2Consumer is VRFConsumerBaseV2, ConfirmedOwner {
event RequestSent(uint256 requestId, uint32 numWords);
event RequestFulfilled(uint256 requestId, uint256[] randomWords);
struct RequestStatus {
bool fulfilled; // whether the request has been successfully fulfilled
bool exists; // whether a requestId exists
uint256[] randomWords;
}
mapping(uint256 => RequestStatus) public s_requests; /* requestId --> requestStatus */
VRFCoordinatorV2Interface COORDINATOR;
uint64 s_subscriptionId;
// past requests Id.
uint256[] public requestIds;
uint256 public lastRequestId;
bytes32 keyHash = 0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15;
uint32 callbackGasLimit = 100000;
uint16 requestConfirmations = 3;
uint32 numWords = 2;
constructor(uint64 subscriptionId) VRFConsumerBaseV2(0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D) ConfirmedOwner(msg.sender) {
COORDINATOR = VRFCoordinatorV2Interface(0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D);
s_subscriptionId = subscriptionId;
}
// 실제 random 값을 뱉어내는 함수
function requestRandomWords() external onlyOwner returns (uint256 requestId) {
requestId = COORDINATOR.requestRandomWords(
keyHash,
s_subscriptionId,
requestConfirmations,
callbackGasLimit,
numWords
);
s_requests[requestId] = RequestStatus({
randomWords: new uint256[](0),
exists: true,
fulfilled: false
});
requestIds.push(requestId);
lastRequestId = requestId;
emit RequestSent(requestId, numWords);
return requestId;
}
// 꼭 구현되어야 하는 vrf callback function
function fulfillRandomWords(uint256 _requestId,uint256[] memory _randomWords) internal override {
require(s_requests[_requestId].exists, "request not found");
s_requests[_requestId].fulfilled = true;
s_requests[_requestId].randomWords = _randomWords;
emit RequestFulfilled(_requestId, _randomWords);
}
// 랜덤 값을 requestID로 받아오는 함수
function getRequestStatus(uint256 _requestId) external view returns (bool fulfilled, uint256[] memory randomWords) {
require(s_requests[_requestId].exists, "request not found");
RequestStatus memory request = s_requests[_requestId];
return (request.fulfilled, request.randomWords);
}
// 1. 0부터 99까지 임의의 숫자, 위의 getRequestStatus를 응용한 함수
function zerto99(uint256 _requestId) external view returns (bool fulfilled, uint256[] memory randomWords) {
require(s_requests[_requestId].exists, "request not found");
RequestStatus memory request = s_requests[_requestId];
uint a = request.randomWords[0];
uint[] memory b = divideNumber(a);
return (request.fulfilled, b);
}
// 2. 주사위(1~6), 위의 getRequestStatus를 응용한 함수
function rollingDice(uint256 _requestId) external view returns (bool, uint256) {
require(s_requests[_requestId].exists, "request not found");
RequestStatus memory request = s_requests[_requestId];
uint a = request.randomWords[0];
uint b = a%6+1;
return (request.fulfilled, b);
/* uint32 numWords = 2; => 1로 수정,
위에 있는 uint a = request.randomWords[0];를 uint a = request.randomWords;
*/
}
function getLength(uint _n) public pure returns(uint) {
if(_n==0) {
return 1;
}
uint a;
while(_n >= 10**a) {
a++;
}
return a;
}
function divideNumber(uint _n) public pure returns(uint[] memory) {
uint[] memory b = new uint[]((getLength(_n)+1)/2);
uint i=0;
while(_n !=0) {
b[i++] = _n%100;
_n = _n/100;
}
return (b);
}
}