Skip to content

Commit

Permalink
script to move link distribution from old distributor to new
Browse files Browse the repository at this point in the history
  • Loading branch information
BkChoy committed Mar 22, 2024
1 parent beff58e commit 514d97d
Show file tree
Hide file tree
Showing 3 changed files with 318 additions and 1 deletion.
231 changes: 231 additions & 0 deletions contracts/test/Multicall3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/// @title Multicall3
/// @notice Aggregate results from multiple function calls
/// @dev Multicall & Multicall2 backwards-compatible
/// @dev Aggregate methods are marked `payable` to save 24 gas per call
/// @author Michael Elliot <[email protected]>
/// @author Joshua Levine <[email protected]>
/// @author Nick Johnson <[email protected]>
/// @author Andreas Bigger <[email protected]>
/// @author Matt Solomon <[email protected]>
contract Multicall3 {
struct Call {
address target;
bytes callData;
}

struct Call3 {
address target;
bool allowFailure;
bytes callData;
}

struct Call3Value {
address target;
bool allowFailure;
uint256 value;
bytes callData;
}

struct Result {
bool success;
bytes returnData;
}

/// @notice Backwards-compatible call aggregation with Multicall
/// @param calls An array of Call structs
/// @return blockNumber The block number where the calls were executed
/// @return returnData An array of bytes containing the responses
function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData) {
blockNumber = block.number;
uint256 length = calls.length;
returnData = new bytes[](length);
Call calldata call;
for (uint256 i = 0; i < length; ) {
bool success;
call = calls[i];
(success, returnData[i]) = call.target.call(call.callData);
require(success, "Multicall3: call failed");
unchecked {
++i;
}
}
}

/// @notice Backwards-compatible with Multicall2
/// @notice Aggregate calls without requiring success
/// @param requireSuccess If true, require all calls to succeed
/// @param calls An array of Call structs
/// @return returnData An array of Result structs
function tryAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (Result[] memory returnData) {
uint256 length = calls.length;
returnData = new Result[](length);
Call calldata call;
for (uint256 i = 0; i < length; ) {
Result memory result = returnData[i];
call = calls[i];
(result.success, result.returnData) = call.target.call(call.callData);
if (requireSuccess) require(result.success, "Multicall3: call failed");
unchecked {
++i;
}
}
}

/// @notice Backwards-compatible with Multicall2
/// @notice Aggregate calls and allow failures using tryAggregate
/// @param calls An array of Call structs
/// @return blockNumber The block number where the calls were executed
/// @return blockHash The hash of the block where the calls were executed
/// @return returnData An array of Result structs
function tryBlockAndAggregate(
bool requireSuccess,
Call[] calldata calls
) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
blockNumber = block.number;
blockHash = blockhash(block.number);
returnData = tryAggregate(requireSuccess, calls);
}

/// @notice Backwards-compatible with Multicall2
/// @notice Aggregate calls and allow failures using tryAggregate
/// @param calls An array of Call structs
/// @return blockNumber The block number where the calls were executed
/// @return blockHash The hash of the block where the calls were executed
/// @return returnData An array of Result structs
function blockAndAggregate(
Call[] calldata calls
) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData) {
(blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls);
}

/// @notice Aggregate calls, ensuring each returns success if required
/// @param calls An array of Call3 structs
/// @return returnData An array of Result structs
function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData) {
uint256 length = calls.length;
returnData = new Result[](length);
Call3 calldata calli;
for (uint256 i = 0; i < length; ) {
Result memory result = returnData[i];
calli = calls[i];
(result.success, result.returnData) = calli.target.call(calli.callData);
assembly {
// Revert if the call fails and failure is not allowed
// `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
// set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
// set data offset
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
// set length of revert string
mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
// set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
revert(0x00, 0x64)
}
}
unchecked {
++i;
}
}
}

/// @notice Aggregate calls with a msg value
/// @notice Reverts if msg.value is less than the sum of the call values
/// @param calls An array of Call3Value structs
/// @return returnData An array of Result structs
function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData) {
uint256 valAccumulator;
uint256 length = calls.length;
returnData = new Result[](length);
Call3Value calldata calli;
for (uint256 i = 0; i < length; ) {
Result memory result = returnData[i];
calli = calls[i];
uint256 val = calli.value;
// Humanity will be a Type V Kardashev Civilization before this overflows - andreas
// ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256
unchecked {
valAccumulator += val;
}
(result.success, result.returnData) = calli.target.call{value: val}(calli.callData);
assembly {
// Revert if the call fails and failure is not allowed
// `allowFailure := calldataload(add(calli, 0x20))` and `success := mload(result)`
if iszero(or(calldataload(add(calli, 0x20)), mload(result))) {
// set "Error(string)" signature: bytes32(bytes4(keccak256("Error(string)")))
mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
// set data offset
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
// set length of revert string
mstore(0x24, 0x0000000000000000000000000000000000000000000000000000000000000017)
// set revert string: bytes32(abi.encodePacked("Multicall3: call failed"))
mstore(0x44, 0x4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000)
revert(0x00, 0x84)
}
}
unchecked {
++i;
}
}
// Finally, make sure the msg.value = SUM(call[0...i].value)
require(msg.value == valAccumulator, "Multicall3: value mismatch");
}

/// @notice Returns the block hash for the given block number
/// @param blockNumber The block number
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
blockHash = blockhash(blockNumber);
}

/// @notice Returns the block number
function getBlockNumber() public view returns (uint256 blockNumber) {
blockNumber = block.number;
}

/// @notice Returns the block coinbase
function getCurrentBlockCoinbase() public view returns (address coinbase) {
coinbase = block.coinbase;
}

/// @notice Returns the block difficulty
function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
difficulty = block.difficulty;
}

/// @notice Returns the block gas limit
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
gaslimit = block.gaslimit;
}

/// @notice Returns the block timestamp
function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
timestamp = block.timestamp;
}

/// @notice Returns the (ETH) balance of a given address
function getEthBalance(address addr) public view returns (uint256 balance) {
balance = addr.balance;
}

/// @notice Returns the block hash of the last block
function getLastBlockHash() public view returns (bytes32 blockHash) {
unchecked {
blockHash = blockhash(block.number - 1);
}
}

/// @notice Gets the base fee of the given block
/// @notice Can revert if the BASEFEE opcode is not implemented by the given chain
function getBasefee() public view returns (uint256 basefee) {
basefee = block.basefee;
}

/// @notice Returns the chain id
function getChainId() public view returns (uint256 chainid) {
chainid = block.chainid;
}
}
86 changes: 86 additions & 0 deletions scripts/balanceMaps/LINK/deprecated/withdraw-link-distribution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import fs from 'fs'
import { ethers } from 'hardhat'

const tokenAddress = '0x514910771af9ca656af840dff83e8264ecf986ca' // address of LINK token
const oldDistributorAddress = '0xe7Dd77d408920c000C40C35c4c111318Ba8B4767' // address of old merkle distributor
const multicallAddress = '0xcA11bde05977b3631167028862bE2a173976CA11' // address of multicall contract
const inputBalanceMapPath = 'scripts/balanceMaps/LINK/2023-04-27.json' // path to input balance map
const outputBalanceMapPath = 'scripts/balanceMaps/LINK/distributor-deprecation.json' // path to output balance map

function getBalanceMap() {
return JSON.parse(
fs.readFileSync(inputBalanceMapPath, {
encoding: 'utf8',
})
)
}

function writeBalanceMap(balanceMap: any) {
fs.writeFileSync(outputBalanceMapPath, JSON.stringify(balanceMap, null, 1))
}

async function main() {
const distributor = (await ethers.getContractAt(
'MerkleDistributor',
oldDistributorAddress
)) as any
const token = await ethers.getContractAt('ERC20', tokenAddress)
const multicall = await ethers.getContractAt('Multicall3', multicallAddress)

console.log('Pausing merkle distributor...')
await (await distributor.pauseForWithdrawal(tokenAddress)).wait()

console.log('Generating new balance map...')

const oldDistributorInterface = new ethers.Interface([
'function getClaimed(address token, address account) view',
])
const oldBalanceMap = getBalanceMap()
const accounts = Object.keys(oldBalanceMap)
let claimed: bigint[] = []

for (let i = 0; i < 2000; i += 500) {
const batch = await multicall.aggregate3
.staticCall(
accounts.slice(i, i + 500).map((account) => ({
target: oldDistributorAddress,
allowFailure: false,
callData: oldDistributorInterface.encodeFunctionData('getClaimed', [
tokenAddress,
account,
]),
}))
)
.then((d) => d.map((d) => BigInt(d[1])))
claimed = claimed.concat(batch)
}

const balanceMap: any = {}
accounts.forEach((account, i) => {
balanceMap[account] = (BigInt(oldBalanceMap[account]) - claimed[i]).toString()
})

//adjust for extra LINK sent to distributor
const balance = BigInt(await token.balanceOf(oldDistributorAddress)) - 999999999997289040n
const totalUnclaimed: any = Object.values(balanceMap).reduce(
(prev: any, cur: any) => prev + BigInt(cur),
0n
)

if (Object.keys(balanceMap).length != accounts.length) throw Error('Invalid balance map')
if (totalUnclaimed != balance) throw Error('Invalid balance map')

writeBalanceMap(balanceMap)

console.log('Withdrawing unclaimed tokens...')
await (await distributor.withdrawUnclaimedTokens(tokenAddress, ethers.ZeroHash, 0)).wait()

console.log('Success! Total unclaimed: ', totalUnclaimed)
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})
2 changes: 1 addition & 1 deletion scripts/update-distribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function generateNewTree(oldTreeData: any, balanceMap: any) {
accounts.forEach((account, index) => {
newTreeData[account] = {
index: index.toString(),
amount: (BigInt(balanceMap[account]) + BigInt(oldTreeData[account].amount || 0)).toString(),
amount: (BigInt(balanceMap[account]) + BigInt(oldTreeData[account]?.amount || 0)).toString(),
}
})

Expand Down

0 comments on commit 514d97d

Please sign in to comment.