-
Notifications
You must be signed in to change notification settings - Fork 256
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
Implement multicall #1631
base: main
Are you sure you want to change the base?
Implement multicall #1631
Changes from all commits
25ac860
c753bed
d6570be
5dcdd6c
def7ef7
9d341bc
1748391
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"contractName": "MultiCall", | ||
"abi": [ | ||
{ | ||
"constant": true, | ||
"inputs": [ | ||
{ | ||
"components": [ | ||
{ "name": "target", "type": "address" }, | ||
{ "name": "callData", "type": "bytes" } | ||
], | ||
"name": "calls", | ||
"type": "tuple[]" | ||
} | ||
], | ||
"name": "aggregate", | ||
"outputs": [ | ||
{ "name": "blockNumber", "type": "uint256" }, | ||
{ "name": "returnData", "type": "bytes[]" } | ||
], | ||
"payable": false, | ||
"stateMutability": "view", | ||
"type": "function" | ||
}, | ||
{ | ||
"constant": true, | ||
"inputs": [{ "name": "addr", "type": "address" }], | ||
"name": "getEthBalance", | ||
"outputs": [{ "name": "balance", "type": "uint256" }], | ||
"payable": false, | ||
"stateMutability": "view", | ||
"type": "function" | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { Web3JsAbiCall } from '../../../abi-common'; | ||
|
||
export interface getNFTCall { | ||
abi: any; | ||
calls: callData[]; | ||
} | ||
|
||
export interface callData { | ||
address: string; | ||
name: string; | ||
params: string[]; | ||
} | ||
|
||
export interface returnData { | ||
blockNumber: number; | ||
returnData: string[]; | ||
} | ||
|
||
export interface MultiCall { | ||
aggregate(calldata: string[]): Web3JsAbiCall<returnData>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,13 @@ import Vue from 'vue'; | |
import Vuex from 'vuex'; | ||
import Web3 from 'web3'; | ||
import _ from 'lodash'; | ||
import {bnMinimum, currentChainSupportsDrawbridge, currentChainSupportsPvP, currentChainSupportsQuests, toBN} from '@/utils/common'; | ||
import { | ||
bnMinimum, | ||
currentChainSupportsDrawbridge, | ||
currentChainSupportsPvP, | ||
currentChainSupportsQuests, | ||
toBN, | ||
} from '@/utils/common'; | ||
|
||
import {getConfigValue, setUpContracts} from '@/contracts'; | ||
|
||
|
@@ -16,11 +22,17 @@ import {burningManager as featureFlagBurningManager} from '@/feature-flags'; | |
import {ERC20, IERC721, INftStakingRewards, IStakingRewards} from '@/../../build/abi-interfaces'; | ||
import {stakeTypeThatCanHaveUnclaimedRewardsStakedTo} from '@/stake-types'; | ||
import {Nft} from '@/interfaces/Nft'; | ||
import { Interface } from '@ethersproject/abi'; | ||
import {Element} from '@/enums/Element'; | ||
import {getWeaponNameFromSeed} from '@/weapon-name'; | ||
import axios from 'axios'; | ||
import {abi as erc20Abi} from '@/../../build/contracts/ERC20.json'; | ||
import {abi as erc721Abi} from '@/../../build/contracts/IERC721.json'; | ||
import { abi as charactersAbi } from '@/../../build/contracts/Characters.json'; | ||
import { abi as weaponsAbi } from '@/../../build/contracts/Weapons.json'; | ||
import { abi as shieldsAbi } from '@/../../build/contracts/Shields.json'; | ||
import { abi as raidTrinketsAbi } from '@/../../build/contracts/RaidTrinket.json'; | ||
import { abi as junkAbi } from '@/../../build/contracts/Junk.json'; | ||
import BigNumber from 'bignumber.js'; | ||
import bridge from './bridge'; | ||
import pvp from './pvp'; | ||
|
@@ -31,6 +43,7 @@ import land from './land'; | |
import treasury from './treasury'; | ||
import specialWeaponsManager from './specialWeaponsManager'; | ||
import combat from './combat'; | ||
import { getNFTCall } from '@/utils/multicall'; | ||
|
||
const transakAPIURL = process.env.VUE_APP_TRANSAK_API_URL || 'https://staging-global.transak.com'; | ||
const transakAPIKey = process.env.VUE_APP_TRANSAK_API_KEY || '90167697-74a7-45f3-89da-c24d32b9606c'; | ||
|
@@ -1085,23 +1098,57 @@ export default new Vuex.Store<IState>({ | |
await dispatch('fetchSkillBalance'); | ||
}, | ||
|
||
async fetchCharacters({ dispatch }, characterIds: (string | number)[]) { | ||
await Promise.all(characterIds.map(id => dispatch('fetchCharacter', { characterId: id }))); | ||
async fetchCharacters({ state, dispatch }, characterIds: (string | number)[]) { | ||
const { Characters } = state.contracts(); | ||
if (!Characters) return; | ||
|
||
console.log('fetch 1'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A reminder comment to delete console.logs after the code is in a desired shape. |
||
console.log('address: ', Characters?.options.address); | ||
console.log('getNFTCall: ', getNFTCall(charactersAbi, Characters?.options.address, 'get', characterIds.map(characterId => [characterId]))); | ||
const multiCharacterDatas: string[] = await dispatch( | ||
'multicall', | ||
getNFTCall(charactersAbi, Characters?.options.address, 'get', characterIds.map(characterId => [characterId]))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need optional chaining operator "?", as at the start we have |
||
console.log('fetch 2'); | ||
characterIds.forEach((characterId, i) => { | ||
dispatch('fetchCharacter', { characterId, characterData: multiCharacterDatas[i] }); | ||
}); | ||
console.log('fetch 3'); | ||
}, | ||
|
||
async fetchGarrisonCharacters({ dispatch }, garrisonCharacterIds: (string | number)[]) { | ||
await Promise.all(garrisonCharacterIds.map(id => dispatch('fetchCharacter', { characterId: id, inGarrison: true }))); | ||
async fetchGarrisonCharacters({ state, dispatch }, garrisonCharacterIds: (string | number)[]) { | ||
const { Characters } = state.contracts(); | ||
if (!Characters) return; | ||
|
||
const multiCharacterDatas: string[] = await dispatch( | ||
'multicall', | ||
getNFTCall(charactersAbi, Characters?.options.address, 'get', garrisonCharacterIds.map(garrisonCharacterId => [garrisonCharacterId]))); | ||
|
||
garrisonCharacterIds.forEach((garrisonCharacterId, i) => { | ||
dispatch('fetchCharacter', { characterId: garrisonCharacterId, characterData: multiCharacterDatas[i], inGarrison: true }); | ||
}); | ||
}, | ||
|
||
async fetchCharacter({ state, commit, dispatch }, { characterId, inGarrison = false }: { characterId: string | number, inGarrison: boolean}) { | ||
/** | ||
* | ||
* @param param0 object containing references to relevant globals | ||
* @param param1 object containing the main params. They are: | ||
* - characterId: characterId of the character being fetched | ||
* - characterData: the optional character data gotten from use of multiCall | ||
* - inGarrison: true if from fetchGarrisonCharacters | ||
*/ | ||
async fetchCharacter( | ||
{ state, commit, dispatch }, | ||
{ characterId, characterData = [], inGarrison = false }: | ||
{ characterId: string | number, characterData: string[], inGarrison: boolean}) { | ||
|
||
const { Characters } = state.contracts(); | ||
if(!Characters) return; | ||
if (!Characters) return; | ||
|
||
await Promise.all([ | ||
(async () => { | ||
const character = characterFromContract( | ||
characterId, | ||
await Characters.methods.get('' + characterId).call(defaultCallOptions(state)) | ||
characterData.length > 0 ? characterData : await Characters.methods.get('' + characterId).call(defaultCallOptions(state)) | ||
); | ||
await dispatch('fetchCharacterPower', characterId); | ||
await dispatch('getIsCharacterInArena', characterId); | ||
|
@@ -1130,19 +1177,33 @@ export default new Vuex.Store<IState>({ | |
} | ||
}, | ||
|
||
async fetchWeapons({ dispatch }, weaponIds: (string | number)[]) { | ||
await Promise.all(weaponIds.map(id => dispatch('fetchWeapon', id))); | ||
async fetchWeapons({ state, dispatch }, weaponIds: (string | number)[]) { | ||
const { Weapons } = state.contracts(); | ||
if(!Weapons) return; | ||
|
||
const multiWeaponDatas: string[] = await dispatch( | ||
'multicall', | ||
getNFTCall(weaponsAbi, Weapons?.options.address, 'get', weaponIds.map(weaponId => [weaponId]))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, "?" not needed |
||
|
||
weaponIds.forEach((weaponId, i) => { | ||
dispatch('fetchCharacter', { weaponId, weaponData: multiWeaponDatas[i] }); | ||
}); | ||
}, | ||
|
||
async fetchWeapon({ state, commit, dispatch }, weaponId: string | number) { | ||
/** | ||
* | ||
* @param weaponId weaponId of the weapon being fetched | ||
* @param weaponData the optional weapon data gotten from use of multiCall | ||
*/ | ||
async fetchWeapon({ state, commit, dispatch }, weaponId: string | number, weaponData: string[] = []) { | ||
const { Weapons } = state.contracts(); | ||
if(!Weapons) return; | ||
|
||
await Promise.all([ | ||
(async () => { | ||
const weapon = weaponFromContract( | ||
weaponId, | ||
await Weapons.methods.get('' + weaponId).call(defaultCallOptions(state)) | ||
weaponData.length > 0 ? weaponData : await Weapons.methods.get('' + weaponId).call(defaultCallOptions(state)) | ||
); | ||
|
||
commit('updateWeapon', { weaponId, weapon }); | ||
|
@@ -1155,59 +1216,102 @@ export default new Vuex.Store<IState>({ | |
if(!Shields || !state.defaultAccount) return; | ||
return await Shields.methods.getNftVar(shieldId, 2).call(defaultCallOptions(state)); | ||
}, | ||
async fetchShields({ dispatch }, shieldIds: (string | number)[]) { | ||
await Promise.all(shieldIds.map(id => dispatch('fetchShield', id))); | ||
|
||
async fetchShields({ state, dispatch }, shieldIds: (string | number)[]) { | ||
const { Shields } = state.contracts(); | ||
if(!Shields) return; | ||
|
||
const multiShieldDatas: string[] = await dispatch( | ||
'multicall', | ||
getNFTCall(shieldsAbi, Shields?.options.address, 'get', shieldIds.map(shieldId => [shieldId]))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, "?" not needed |
||
|
||
shieldIds.forEach((shieldId, i) => { | ||
dispatch('fetchShield', { shieldId, shieldData: multiShieldDatas[i] }); | ||
}); | ||
}, | ||
|
||
async fetchShield({ state, commit }, shieldId: string | number) { | ||
/** | ||
* | ||
* @param shieldId shieldId of the shield being fetched | ||
* @param shieldData the optional shield data gotten from use of multiCall | ||
*/ | ||
async fetchShield({ state, commit }, shieldId: string | number, shieldData: string[] = []) { | ||
const { Shields } = state.contracts(); | ||
if(!Shields) return; | ||
|
||
await Promise.all([ | ||
(async () => { | ||
const shield = shieldFromContract( | ||
shieldId, | ||
await Shields.methods.get('' + shieldId).call(defaultCallOptions(state)) | ||
shieldData.length > 0 ? shieldData : await Shields.methods.get('' + shieldId).call(defaultCallOptions(state)) | ||
); | ||
|
||
commit('updateShield', { shieldId, shield }); | ||
})(), | ||
]); | ||
}, | ||
|
||
async fetchTrinkets({ dispatch }, trinketIds: (string | number)[]) { | ||
await Promise.all(trinketIds.map(id => dispatch('fetchTrinket', id))); | ||
async fetchTrinkets({ state, dispatch }, trinketIds: (string | number)[]) { | ||
const { RaidTrinket } = state.contracts(); | ||
if(!RaidTrinket) return; | ||
|
||
const multiTrinketDatas: string[] = await dispatch( | ||
'multicall', | ||
getNFTCall(raidTrinketsAbi, RaidTrinket?.options.address, 'get', trinketIds.map(trinketId => [trinketId]))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, "?" not needed |
||
|
||
trinketIds.forEach((trinketId, i) => { | ||
dispatch('fetchTrinket', { trinketId, trinketData: multiTrinketDatas[i] }); | ||
}); | ||
}, | ||
|
||
async fetchTrinket({ state, commit }, trinketId: string | number) { | ||
/** | ||
* | ||
* @param trinketId trinketId of the trinket being fetched | ||
* @param trinketData the optional trinket data gotten from use of multiCall | ||
*/ | ||
async fetchTrinket({ state, commit }, trinketId: string | number, trinketData: string[] = []) { | ||
const { RaidTrinket } = state.contracts(); | ||
if(!RaidTrinket) return; | ||
|
||
await Promise.all([ | ||
(async () => { | ||
const trinket = trinketFromContract( | ||
trinketId, | ||
await RaidTrinket.methods.get('' + trinketId).call(defaultCallOptions(state)) | ||
trinketData.length > 0 ? trinketData : await RaidTrinket.methods.get('' + trinketId).call(defaultCallOptions(state)) | ||
); | ||
|
||
commit('updateTrinket', { trinketId, trinket }); | ||
})(), | ||
]); | ||
}, | ||
|
||
async fetchJunks({ dispatch }, junkIds: (string | number)[]) { | ||
await Promise.all(junkIds.map(id => dispatch('fetchJunk', id))); | ||
async fetchJunks({ state, dispatch }, junkIds: (string | number)[]) { | ||
const { Junk } = state.contracts(); | ||
if(!Junk) return; | ||
|
||
const multiJunkDatas = await dispatch( | ||
'multicall', | ||
getNFTCall(junkAbi, Junk?.options.address, 'get', junkIds.map(junkId => [junkId]))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, "?" not needed |
||
|
||
junkIds.forEach((junkId, i) => { | ||
dispatch('fetchJunk', { junkId, junkData: multiJunkDatas[i] }); | ||
}); | ||
}, | ||
|
||
async fetchJunk({ state, commit }, junkId: string | number) { | ||
/** | ||
* | ||
* @param junkId junkId of the junk being fetched | ||
* @param junkData the optional junk data gotten from use of multiCall | ||
*/ | ||
async fetchJunk({ state, commit }, junkId: string | number, junkData: string = '') { | ||
const { Junk } = state.contracts(); | ||
if(!Junk) return; | ||
|
||
await Promise.all([ | ||
(async () => { | ||
const junk = junkFromContract( | ||
junkId, | ||
await Junk.methods.get('' + junkId).call(defaultCallOptions(state)) | ||
junkData ? junkData : await Junk.methods.get('' + junkId).call(defaultCallOptions(state)) | ||
); | ||
|
||
commit('updateJunk', { junkId, junk }); | ||
|
@@ -2769,5 +2873,18 @@ export default new Vuex.Store<IState>({ | |
|
||
return CryptoBlades.methods.getMintCharacterFee().call(defaultCallOptions(state)); | ||
}, | ||
|
||
async multicall({state}, {abi, calls}) { | ||
console.log('in multiCall'); | ||
const { MultiCall } = state.contracts(); | ||
const itf = new Interface(abi); | ||
const data = calls.map((call: any) => [ | ||
call.address.toLowerCase(), | ||
itf.encodeFunctionData(call.name, call.params), | ||
]); | ||
const { returnData } = await MultiCall.methods.aggregate(data).call(defaultCallOptions(state)) || []; | ||
const res = returnData.map((call, i) => itf.decodeFunctionResult(calls[i].name, call)); | ||
return res; | ||
}, | ||
} | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { callData, getNFTCall } from '@/interfaces'; | ||
|
||
export function getNFTCall(abi: any, address: any, name: string, params: any[]): getNFTCall { | ||
const calls: callData[] = params.map((param: string[]) => ({ | ||
address, | ||
name, | ||
params: param, | ||
})); | ||
return { | ||
abi, | ||
calls, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to keep line 109
if(process.env.NODE_ENV === 'development') return '';
This was originally commented out, and it isn't in CB-Marketplace, which is the only reason this would work in a dev environment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to leave it, as it's in place for taking the address from generated abi files (as the addresses change every time you setup a dev env). I don't think we have multicall in our migrations, so it won't load it correctly. We somehow need to address this issue,