目標
用vrf寫一個隨機紅包
資料結構
紅包:
struct Envelope {
Type t; // 型別,只是erc20 和eth紅包
ERC20 token; // erc20 ,如果是erc20紅包,這裡是erc2o的地址
address sender; // 發紅包的sender
uint balance; // 金額
bool allowAll; // 允許所有人領取
uint32 maxReceiver; // 最大領取數,eg:最多3個人領取紅包
bool avg; // 平均主義,每個紅包的價值等於balance/maxReceiver //填false則使用隨機紅包
uint avgMonty; // 平均金額
uint timeOutBlocks; //超時可以回收紅包 ,也可以開啟未領取完的紅包
address[] received; // 已經領取過的列表
}
每個紅包中儲存紅包的資訊,可以在允許過程中傳送紅包
儲存資料:
mapping(bytes32 => Envelope) public envelopes; // 紅包hash -> 紅包內容,領取紅包時需要提供紅包hash
mapping(bytes32 => mapping(address => bool)) public addressAllowList; // 紅包對應的 allowlist 這個放在紅包外面存是因為struct裡面不能放map
mapping(bytes32 => mapping(address => bool)) addressGotList; // 已經領取的列表,與received有點重複,建議將received修改為一個int,記錄有多少人領過
mapping(uint => bytes32) openWithVRF; // 紅包對應的vrf, 當紅包是隨即紅包時會用到這個
mapping(ERC20 => uint) ERC20Balance; // 每個erc20對應的金額,合約自己可以透過看自己的eth餘額來判斷,erc20需要單獨記錄,應為可以同時存在多個合約,如果多個合約都是同一個erc20,需要判斷erc20的approve是否足夠
mapping(bytes32 => uint[]) VRFKey; // vrf 對應的隨機數列表
// VRFV2PlusClient public COORDINATOR;
bytes32 keyHash = 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae; // VRF 用到的key,可以去官方文件查
uint256 s_subscriptionId; // vrf 用到的另一個key
uint32 immutable callbackGasLimit = 100000; // 官方推薦配置
// The default is 3, but you can set this higher.
uint16 immutable requestConfirmations = 3; //官方推薦配置
上面的mapping 主要是紅包合約的配置
下方的資料則是chainlink vrf的配置,這些key可以去官網檢視具體的含義
合約的初始化
constructor(
uint256 _subscriptionId,
address _coordinator
) VRFConsumerBaseV2Plus(_coordinator){
s_subscriptionId = _subscriptionId;
}
初始化主要是賦值vrf的訂閱id(後續具體操作有詳細過程)
構建紅包
function createETHredEnvelope(
bool allowAll,
address[] memory allowList,
uint32 maxReceiver,
bool avg,
uint timeOutBlocks
) public payable returns (bytes32) {}
- allowAll:執行所有人領取,如果是true那麼任何人都可以根據紅包hash呼叫get方法領取紅包
- allowList:如果allowAll,那麼allowList無用
- maxReceiver:最大領取數,最大領取數可以比allowList小,這樣代表有人領不到紅包
- avg: 是否平均,如果平均那麼每個人領取到的金額 = msg.value/maxReceiver
- timeOutBlocks:經過多少各block後超時,超時之後紅包的發起人可以回收紅包餘額,或者開啟紅包
function createERC20redEnvelope(
address token,
uint size, // erc20的數量
bool allowAll,
address[] memory allowList,
uint32 maxReceiver,
bool avg,
uint timeOutBlocks
) public returns (bytes32) {}
createERC20redEnvelope
與createETHredEnvelope
的區別只是使用的是erc20
函式內的區別在與erc20要校驗有沒有足夠的apporve
uint approved = ERC20(token).allowance(msg.sender,address(this));
require(approved>=ERC20Balance[ERC20(token)]+size);
ERC20Balance[ERC20(token)] += size;
新增AllowList
function allowSome(bytes32 hash, address[] memory allowList) public {
require(envelopes[hash].balance != 0, "envelop balance is 0");
require(envelopes[hash].sender == msg.sender,"only envelops sender can do this");
for (uint i = 0; i < allowList.length; i++) {
addressAllowList[hash][allowList[i]] = true;
}
}
這就不多解釋了
領取紅包
function get(bytes32 hash) public {}
領紅包的方法簽名很簡單,只需要傳一個紅包hash就可以,但內部邏輯很複雜,重點看一下它裡面的判斷
require(envelopes[hash].balance != 0, "envelop balance is 0"); // 判讀紅包餘額不為0
require(!addressGotList[hash][msg.sender], "has got"); // 判斷髮起人是否已經領取過
require( // 判斷紅包是否已經超時
envelopes[hash].timeOutBlocks > block.number,
"envelop timeOutBlocks is not enough"
);
require(
addressAllowList[hash][msg.sender] || envelopes[hash].allowAll,// 判斷髮起人是否被允許
"not allow"
);
require(envelopes[hash].received.length < envelopes[hash].maxReceiver, "no more"); // 還是判斷是否已經領取完
在領取上有兩種邏輯,一種是平均紅包,平均紅包get後會馬上到賬。一種是隨機數紅包,隨機數紅包不會立馬到賬需要等領紅包的人數達到maxReceiver 或者紅包超時,後面會詳細講怎麼領隨機數紅包。
開啟隨機數紅包
function openEnvelopes(bytes32 hash)public{
require(
envelopes[hash].timeOutBlocks < block.number || envelopes[hash].received.length == envelopes[hash].maxReceiver,
"envelop timeOutBlocks is not enough"
);
require(envelopes[hash].maxReceiver > 0,"max receriver max more than 0");
開啟隨機數紅包一般是在領取時自動呼叫,如果領取人沒有達到maxReciver,可以在紅包超時後手動呼叫。
這個方法中會向vrf請求一個隨機數,正常情況chainlink會呼叫fulfillRandomWords方法來返回隨機數。
function fulfillRandomWords(
uint256 requestId,
uint256[] calldata randomWords
) internal override {
require(randomWords.length == envelopes[openWithVRF[requestId]].received.length);
VRFKey[openWithVRF[requestId]] = randomWords;
}
實際上可以在這個方法裡面寫紅包分發的內容,但是由於這一步是chainlink觸發的是由他來執行手續費,所以這裡面邏輯不能太複雜(實際上限制的引數就是keyHash 這個變數)
手動開啟紅包
由於chainlink返回的時候不能有複雜的邏輯,所以隨機數紅包只能由手動觸發
function openVRFEnvelop(bytes32 hash)public {
uint[] memory randomWords = VRFKey[hash];
require(envelopes[hash].maxReceiver > 0,"max receriver max more than 0");
require(randomWords.length!=0,"can not get vrf words");
uint16[] memory words = new uint16[](randomWords.length);
// 計算每一個小分段的權重
}
vrf訂閱id獲取
首先我們需要去chainlink上領取一點測試幣(link幣和eth幣,兩個都要,如有已經有了可以跳過)
網址: https://faucets.chain.link/
然後需要去crf管理頁面構建一個錢包合約,後面請求vrf隨機數時會扣除Link幣
網址:https://vrf.chain.link/
填完資訊後,還是這樣網址,下面會出現你的sub
點選你的sub,裡面有sub的id,這個id就是合約部署時要用到的id,可以用這個id先把合約部署上去,後面要合約的地址
在這個頁面的右下角找到fund 給這個sub衝點link幣
衝完之後點左邊的add cousumer ,把你的合約地址填進來
至此,這個紅包合約就可以用了
測試
這個紅包我已經部署在測試網路上了,可以直接去上面試試
https://sepolia.etherscan.io/address/0xc81c0913e6365eb31e761d1062b41dd5a96d2e90#writeContract
合約原始碼後續會貼在這裡(今天網太卡了,我環境一直下載不下來)
原始碼地址:(這兩天環境弄好了我會把程式碼放上去,目前還是一個空專案)
https://github.com/bighu630/redEnvelop