Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sidechain distribution contract #2

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
96 changes: 96 additions & 0 deletions contracts/veDistributionSnapshot.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: bsl-1.1
/**
* Copyright 2022 Unit Protocol V2: Artem Zakharov (hello@unit.xyz).
*/
pragma solidity ^0.8.0;
Eenae marked this conversation as resolved.
Show resolved Hide resolved

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/**
* @notice Rewards distribution in sidechains to veDUCK holders
* Snapshot of holders in mainnet is taken once
*/
contract veDistributionSnapshot is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;

/** @notice balance of user from snapshot */
mapping(address => uint) public balanceOf;
/** @notice users list */
address[] public users;
/** @notice sum of users' balances from snapshot */
uint public totalSupply;

/** @notice amounts of reward token already sent to all users */
mapping(IERC20 => uint) public rewardsSent;
/** @notice amounts of reward token already sent to user */
mapping(IERC20 => mapping (address => uint)) public rewardsSentToUser;

event RewardSent(IERC20 indexed token, address indexed user, uint amount);

/**
* @notice add users' balances from snapshot
* @dev after all balances added `renounceOwnership` must be called
*/
function addBalances(address[] calldata users_, uint[] calldata balances_) public onlyOwner {
require(users_.length > 0, "DISTRIBUTION: EMPTY_ARRAYS");
require(users_.length == balances_.length, "DISTRIBUTION: INVALID_ARRAYS_LENGTH");

for (uint i; i < users_.length; i++) {
require(balances_[i] > 0, "DISTRIBUTION: INVALID_AMOUNT");
require(balanceOf[users_[i]] == 0, "DISTRIBUTION: USER_ALREADY_ADDED");

balanceOf[users_[i]] = balances_[i];
totalSupply += balances_[i];
users.push(users_[i]);
}
}

function usersCount() public view returns (uint) {
return users.length;
}

function allUsers() public view returns (address[] memory) {
return users;
}

function availableReward(address user_, IERC20 token_) public view returns (uint) {
uint userBalance = balanceOf[user_];
if (userBalance == 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps, zero should be returned also during the snapshot population, since rewards may jump misleadingly during that process.

return 0;
}

return _calcTotalUserReward(userBalance, token_) - rewardsSentToUser[token_][user_];
}

function withdrawReward(IERC20[] calldata tokens_) public nonReentrant {
require(owner() == address(0), 'DISTRIBUTION: CONTRACT_IS_NOT_FINALIZED');

for (uint i; i < tokens_.length; i++) {
_withdrawReward(tokens_[i]);
}
}

function _withdrawReward(IERC20 token_) internal {
uint userBalance = balanceOf[msg.sender];
require(userBalance > 0, 'DISTRIBUTION: AUTH_FAILED');

uint totalUserReward = _calcTotalUserReward(userBalance, token_);
require(totalUserReward > rewardsSentToUser[token_][msg.sender], 'DISTRIBUTION: NOTHING_TO_WITHDRAW');

uint amountToSend = totalUserReward - rewardsSentToUser[token_][msg.sender];
rewardsSentToUser[token_][msg.sender] += amountToSend;
rewardsSent[token_] += amountToSend;

token_.safeTransfer(msg.sender, amountToSend);
emit RewardSent(token_, msg.sender, amountToSend);
}

function _calcTotalUserReward(uint userBalance_, IERC20 token_) internal view returns (uint) {
uint totalRewardsReceived = rewardsSent[token_] + token_.balanceOf(address(this));

return totalRewardsReceived * userBalance_ / totalSupply;
}
}
2 changes: 1 addition & 1 deletion hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ task("accounts", "Prints the list of accounts", async () => {
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.8.7",
solidity: "0.8.17",
mocha: {
timeout: 120000
},
Expand Down
157 changes: 157 additions & 0 deletions scripts/deploySnapshotDistribution.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
const hre = require("hardhat");
const {ethers} = require("hardhat");

// snapshot of veduck holders on 01.09.2022
// Contract 0x48DdD27a4d54CD3e8c34F34F7e66e998442DBcE3
// Block number 15449617
const snapshot = [
['0x0004d2A2f9a823C1A585fDE6514A17FF695E0001','785041821771336119010075'],
['0x002183113F3Fedc5B494080954E5E8FAeB070Fd3','102357982353797689981100'],
['0x00b1e61B175d87cD2824fD6a5cB730da3301f990','9235407280537236587875'],
['0x022Ce4715b44EF6F0eAd8561B29dA676928D16f3','23604134692103753624375'],
['0x056AE750B6fa8501f5bC9c8Acb0caF986Fa224ef','103293287692205155779600'],
['0x0898a4c1E8602A2BC06446eD30b24CD91D65000f','78015484716456325'],
['0x08e305F123FaFf04A6A24243f47984A7A9702417','10485337644163974001250'],
['0x09173487b272311Edda01F45f97911aEB6aBd602','421068210790444710233125'],
['0x0Cd1d691B6aE336302e882eB6DC3e9983BcA74E7','3472832476247227492750'],
['0x0e43C2EBb8BbFBD724A2CCC0f8e0Ea336a00C401','55520559836377380266900'],
['0x11c9Ac11ce9913E26faA7a9EE5B07c92b0C8c372','154493319661379826143950'],
['0x1574472D2FC12905b73f7269352ca4aeFd88eDDf','6452426528115488339050'],
['0x1BE537FF2Cc291caF8331eFcd3466fE7D1C80D4b','267856640078916417269350'],
['0x1aF8d7F66e7043c9Bf52286385bf4d65D73204F6','42886953300854096206575'],
['0x1c012b03f1c2DEa274D2eeEb566B0Eeabfe3Af1A','125137609376199163047475'],
['0x1fFc4d0c003ddf386B93Aa099a2071cd92EbADE1','22822082803493243711700'],
['0x23B886AcEEb71458C96792ffd447352c277f820e','121552551954087383931025'],
['0x2DB04D075c2A96E65d8beef44879ED8d07167384','739485182519099843310375'],
['0x2Fe9811E6B3ccEb5c14cCa6523F10FFDf4288aF6','28255722490506934519200'],
['0x2b0f7b89454dC0Cdf2756A4d574a482d2Ba84C87','8130011652819231505957900'],
['0x2bbfb15f1A903AB3579162A00F81821926422b0d','235890470414763981168175'],
['0x3037DB6bE2AFd39F5ab4006AD2c1Cb33Ddd5F8c7','81084569635151915932150'],
['0x3070f20f86fDa706Ac380F5060D256028a46eC29','18699801009911170589475'],
['0x32f1dEe89452cD5A82C937006F49e9e8f12ceE6B','627423966570942642927450'],
['0x3310E743fb7E596BE28DD7E6AB73Aa9e0469B719','673921995054941756150'],
['0x367Ad4160a1cf17B05FA0699c593bCcC977E47cC','348259482847948455555500'],
['0x37442eD85530B3ccA29339CB0a773ac229b91073','68766488219674396257375'],
['0x3aFB0B4cA9aB60165E207CB14067B07A04114413','7987555502061029359525'],
['0x3eDebBC4223c565c66305Bf3beE9Cfc4BD0f30c9','4810213235041049001425'],
['0x42C53684147bD03645DF294Ca7aC57fDfAcEbfDF','13884060080755858299975'],
['0x4444A136b1445B1f5e4a20656854adCF6Fa3d667','120439665431421237957075'],
['0x45178Bd7bEdb3B394F1105bfCa466Ed6527229a1','402619128801012895875'],
['0x45385eE71E2F6C3e9aF9BAD0bfD69788F1C59E35','43256991153309932585125'],
['0x46bcF9c0f0A59EdA5D2350Abb5d584c07cD61D0B','129452649353120238750775'],
['0x4C1968ede8d9b83160d8CE910C5913f157023447','111032889649899737391425'],
['0x4c2aC40DE55ebB662370CA132aE7dfbF111F2324','1078132451245351965679700'],
['0x4c7E08418382a962fb8abB15b78cBC8cb9b0C222','607294271525512376100'],
['0x4da4A2f69Cb2f61A49EfB7423B1F093B79C6f1F2','252974774069739139115000'],
['0x53b015573630d248a32b11b21E5443EFb92D9CFB','5431960649764375098700'],
['0x55B0BC048011D59675E20e5760609e665683E342','1031934913075529778750'],
['0x5668EAd1eDB8E2a4d724C8fb9cB5fFEabEB422dc','46584483532545011571725'],
['0x56E773B0a620205E9f72265b6013b65B84c2c4aF','6336971686585158118175'],
['0x5D9FbFCef7B3Ea6344dbB2F6bCE9aA3067aDfE75','3020549927067460671675'],
['0x5f3Bce4B242d00ED748d48172C1f2D47A0bcB19B','13530233070582946296350'],
['0x5f69205af146eD017FBfe7A99A1E668eb0152ba2','2998978111986067083250'],
['0x60ec3C53FDd8358e7E68d49b1e4608FA4050A35b','4069735719099036518750'],
['0x656B764DD52c14ddfAf1d8c48E6b8B217440Dbb1','10569845783818332807000'],
['0x6C6F95aFfBB1753115FbAeC513cE0D21E8481d46','1936544777859324875'],
['0x6C82aba562f2e38a5d474CD610F59e6F90774a6a','448131801764671499797975'],
['0x6a41278687f2E431b034D209AE3c9DC16840f24F','2808599968030961346274875'],
['0x6b879dD45cf492B0950e4Ac94f2657D811341169','17316668533778846178650'],
['0x6dee051c76b6996a9992C73D03aA884826C3a087','89308242204152212952925'],
['0x6f9BB7e454f5B3eb2310343f0E99269dC2BB8A1d','166619717612041569412825'],
['0x740E7DAD9F7F5E3E7daCefb13675F574ee918ddb','5168130478868742182600'],
['0x7dcd10df1FbB7F7045AB6bBfC96Fe4709e25FD8C','28528029751117764255650'],
['0x80cD6493242917B17ba8B342B96F94F972e55eeE','195376761875317033198550'],
['0x8442e4FCbbA519B4f4C1EA1FcE57a5379C55906C','354794916900601375'],
['0x8583068704e9A9eBe8bA4FdDCafc57007E7FdADa','30036277052126872290000'],
['0x887C3599c4826F7b3cDe82003b894430F27d5b92','4155787421121863899700'],
['0x8C8024bf5f90a06CCeD7D32BAbcCB934942c82f6','9693961071929327392300'],
['0x9343d80eb115B25285044419AE9dB4913F6b8cda','248166118682737529275'],
['0x93ab7580493Da8e78a31c31250d3C5524B7FFD0E','3467387509515246877075'],
['0x9500178853739b656E30A3E2e25995A131E4EBC0','19486696673323697109266250'],
['0xB10a17fa1bD936F1058d9eAb01a742A7b60272D0','227497530374658711656425'],
['0xB2dd7f7a0c94364067aDE38aB28Ad18d124bB329','194506469928846961435675'],
['0xB5383501116a91C43664E18fe935B7C8957cd125','14104064227271008410300'],
['0xBcF62BF928883ba87f8353C6C421542329250341','626860552134778058791300'],
['0xC0701665EBE8c8530fB04401Abce4b2EF7739473','73662830052942787254325'],
['0xC2A639C930a3E931171BE1136cA400D2B5138324','611753330326445010100'],
['0xCcd358b112515Ab4E9c43D57E6269e783d3131C6','26571317765747818325150'],
['0xCf8fcb37b7E4Af7E2F79A6ff813B4d899Ae70bDc','1707053908444300033451875'],
['0xD952fB344DDAcC0c0e8c23cc686C10d5Dda11A97','61849951424929525220000'],
['0xDa43689C18F625Bd5CA4Ec7bDd2c2E9F9a6D2552','6541017512184648727875'],
['0xDf9E6e4856B104d973629d4E564AAEA7cc9d5E7F','258143588523823003950'],
['0xE1Ea97b43C2d64C5CA71068A2428126b04EA69f5','47192805299691758452750'],
['0xE60DF25872bdb8177E41675bB604D3D08F7d8060','857700220636589604741125'],
['0xEC2D15d079cCA5cF068159B98e0cF87366c1fD9F','209714837757168770527675'],
['0xEC4b1f3184B49191E0a35Ab27FeEa4bf84F7ed2d','4100447342772176866675'],
['0xEaB337618F6E6a3aC744E06b42765116fd81B0D2','407986708792134319328000'],
['0xF0C3118675B9DE25fB6A3d913153521b1c51323F','5667124476788432177754375'],
['0xF2907288f88E234CC5E3b92e579880ce7e39D58D','11348780172795656157348125'],
['0xFF301F3b4a51a1948aE021C77379D5dd56f1377D','167074303545387867379400'],
['0xFbC593fCE7b9909916EE60cA4770708384E2988d','2375102266740702269925'],
['0xa35ACdbfD22Bf313D7BC8e462930eDf1677e2936','78000364206002583844225'],
['0xa7888F85BD76deeF3Bd03d4DbCF57765a49883b3','819141154700272255997650'],
['0xbB4567beF67153E5A4D4Bd1a8c6aae5234b34e2b','92333682744986655681875'],
['0xbaE316407B1187DdF39Fdc7ebEFae0558744FcfE','2490555273876395650100'],
['0xcfdaE6FF195A99Ee0dEd376eaD5056BB679Ebec7','521067208323316786125'],
['0xd6Fd705EE0B31a9300D3E2154bccE777270CBb6f','7462008420246785599500'],
['0xd836da33a6629548271eb9ef4f62D083376eB4A6','13866628813912702611950'],
['0xd8FD1aD0da93D380c96B9DE5F0b6624B03b7963A','131849810534126754738275'],
['0xe8D46499994F4CF0F80fEea5Cfc9D9d502985286','18163985304798561979250'],
['0xeC0c77c2b36283Ca81b4904432F35536d6aDd260','436332123035217389168050'],
['0xf1c1cDF8a84A5e042eb2213623ADaEc37FE21EB6','2103964552030805583342000'],
['0xf2a4cF14022617C8c167bC0E536B9fc08C49f8C3','20585126062584338166625'],
['0xf56036f6a5D9b9991c209DcbC9C40b2C1cD46540','39075352375063386923225'],
['0xfd4ae6890eEf7a664cF3371f1826909e9A07eE2c','43770096516119234922725'],
]

const BATCH_SIZE = 30;

async function main() {
const distribution = await deployContract('veDistributionSnapshot');
console.log("deployed to:", distribution.address);

let i = 0;
let usersCount = 0;
let totalBalance = 0n
while (true) {
const slice = snapshot.slice(i, i + BATCH_SIZE);
if (slice.length === 0) {
break;
}
i += BATCH_SIZE;

let addresses = []
let balances = []
for (const [addr, balance] of slice) {
addresses.push(addr)
balances.push(balance)
usersCount++;
totalBalance += BigInt(balance);
}
await distribution.addBalances(addresses, balances);
}

await distribution.renounceOwnership();

console.log('Owner:', await distribution.owner())
console.log('Users (expected/set):', usersCount, '/', (await distribution.allUsers()).length)
console.log('Total supply (expected/set):', totalBalance, '/', (await distribution.totalSupply()).toBigInt())
}


async function deployContract(contract, ...params) {
const Factory = await ethers.getContractFactory(contract);
const instance = await Factory.deploy(...params);
await instance.deployed();

return instance;
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});