-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #759 from Prop-House/master
Nouns Delegation Strategy & ERC1155 Strategy Bug Fix [Production Release]
- Loading branch information
Showing
29 changed files
with
367 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
...p-house-protocol/contracts/starknet/src/common/power/ethereum_checkpointable_erc721.cairo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
#[starknet::contract] | ||
mod EthereumCheckpointableERC721GovernancePowerStrategy { | ||
use starknet::ContractAddress; | ||
use prop_house::common::utils::constants::{MASK_96, TWO_POW_32}; | ||
use prop_house::common::utils::traits::IGovernancePowerStrategy; | ||
use prop_house::common::libraries::single_slot_proof::SingleSlotProof; | ||
use prop_house::common::utils::storage::{get_slot_key, get_nested_slot_key}; | ||
use array::{ArrayTrait, SpanTrait}; | ||
use option::OptionTrait; | ||
use zeroable::Zeroable; | ||
use traits::Into; | ||
|
||
#[storage] | ||
struct Storage {} | ||
|
||
#[constructor] | ||
fn constructor(ref self: ContractState, fact_registry: ContractAddress, ethereum_block_registry: ContractAddress) { | ||
let mut ssp_state = SingleSlotProof::unsafe_new_contract_state(); | ||
SingleSlotProof::initializer(ref ssp_state, fact_registry, ethereum_block_registry); | ||
} | ||
|
||
#[external(v0)] | ||
impl EthereumCheckpointableERC721GovernancePowerStrategy of IGovernancePowerStrategy<ContractState> { | ||
/// Returns the governance power of the user at the given timestamp. | ||
/// * `timestamp` - The timestamp at which to get the governance power. | ||
/// * `user` - The address of the user. | ||
/// * `params` - The params, containing the contract address and the slot indices. | ||
/// * `user_params` - The user params, containing the slots and MPT proofs. | ||
fn get_power( | ||
self: @ContractState, timestamp: u64, user: felt252, params: Span<felt252>, mut user_params: Span<felt252>, | ||
) -> u256 { | ||
let params_len = params.len(); | ||
|
||
// Expects contract_address, num_checkpoints_slot_index, and checkpoints_slot_index, with an optional governance_power_multiplier | ||
assert(params_len == 3 || params_len == 4, 'EthC721: Bad param length'); | ||
|
||
let contract_address = *params.at(0); | ||
let num_checkpoints_slot_index = *params.at(1); | ||
let checkpoints_slot_index = *params.at(2); | ||
|
||
let (num_checkpoints_user_params, latest_checkpoint_user_params) = Serde::<(Span<felt252>, Span<felt252>)>::deserialize(ref user_params).unwrap(); | ||
|
||
let num_checkpoints_slot = get_slot_key(num_checkpoints_slot_index.into(), user.into()); | ||
let num_checkpoints = SingleSlotProof::get_slot_value( | ||
@SingleSlotProof::unsafe_new_contract_state(), timestamp, contract_address, num_checkpoints_slot, params, num_checkpoints_user_params | ||
); | ||
assert(num_checkpoints.is_non_zero(), 'EthC721: No checkpoints'); | ||
|
||
let latest_checkpoint_slot = get_nested_slot_key(checkpoints_slot_index.into(), array![user.into(), num_checkpoints - 1].span()); | ||
let latest_checkpoint = SingleSlotProof::get_slot_value( | ||
@SingleSlotProof::unsafe_new_contract_state(), timestamp, contract_address, latest_checkpoint_slot, params, latest_checkpoint_user_params | ||
); | ||
let governance_power = (latest_checkpoint / TWO_POW_32) & MASK_96; | ||
|
||
assert(governance_power.is_non_zero(), 'EthC721: No governance power'); | ||
|
||
if params_len == 3 { | ||
return governance_power; | ||
} | ||
|
||
let governance_power_multiplier = *params.at(3); | ||
assert(governance_power_multiplier.is_non_zero(), 'EthC721: Invalid multiplier'); | ||
|
||
governance_power * governance_power_multiplier.into() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
178 changes: 178 additions & 0 deletions
178
packages/prop-house-sdk/src/gov-power/handlers/checkpointable-erc721.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import { ChainConfig, GovPowerStrategyType, GovPowerConfig, AccountField, CheckpointableERC721Config } from '../../types'; | ||
import { SingleSlotProofHandler } from './base'; | ||
import { encoding, splitUint256, storageProofs } from '../../utils'; | ||
import { BigNumber } from '@ethersproject/bignumber'; | ||
import { Contract } from '@ethersproject/contracts'; | ||
import { GET_CURRENT_VOTES_FUNC, NUM_CHECKPOINTS_FUNC } from '../../constants'; | ||
import { Call } from 'starknet'; | ||
|
||
export class CheckpointableERC721Handler extends SingleSlotProofHandler<CheckpointableERC721Config> { | ||
/** | ||
* Information about the Nouns mainnet ERC721 token | ||
*/ | ||
private static readonly _NOUNS = { | ||
ADDRESS: '0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03', | ||
NUM_CHECKPOINTS_SLOT_INDEX: '0x0d', | ||
CHECKPOINTS_SLOT_INDEX: '0x0c', | ||
}; | ||
|
||
/** | ||
* Returns a `BalanceOfHandler` instance for the provided chain configuration | ||
* @param config The chain config | ||
*/ | ||
public static for(config: ChainConfig) { | ||
return new CheckpointableERC721Handler(config); | ||
} | ||
|
||
/** | ||
* The governance power strategy type | ||
*/ | ||
public get type() { | ||
return GovPowerStrategyType.CHECKPOINTABLE_ERC721; | ||
} | ||
|
||
/** | ||
* The governance power strategy address | ||
*/ | ||
public get address() { | ||
return this._addresses.starknet.govPower.checkpointableErc721; | ||
} | ||
|
||
/** | ||
* @notice Get the governance power strategy params that will be shared amongst all users | ||
* @param strategy The governance power strategy information | ||
*/ | ||
public async getStrategyParams(strategy: CheckpointableERC721Config): Promise<string[]> { | ||
if (strategy.address.toLowerCase() !== CheckpointableERC721Handler._NOUNS.ADDRESS) { | ||
throw new Error('This handler currently only supports the Nouns ERC721 token'); | ||
} | ||
if (strategy.multiplier && BigNumber.from(strategy.multiplier).gt(1)) { | ||
return [ | ||
strategy.address, | ||
CheckpointableERC721Handler._NOUNS.NUM_CHECKPOINTS_SLOT_INDEX, | ||
CheckpointableERC721Handler._NOUNS.CHECKPOINTS_SLOT_INDEX, | ||
strategy.multiplier.toString() | ||
]; | ||
} | ||
return [ | ||
strategy.address, | ||
CheckpointableERC721Handler._NOUNS.NUM_CHECKPOINTS_SLOT_INDEX, | ||
CheckpointableERC721Handler._NOUNS.CHECKPOINTS_SLOT_INDEX, | ||
]; | ||
} | ||
|
||
public async getUserParams(account: string, timestamp: string, strategyId: string) { | ||
const strategy = await this.getStrategyAddressAndParams(strategyId); | ||
const [contractAddress, numCheckpointsSlotIndex, checkpointsSlotIndex] = strategy.params; | ||
|
||
const numCheckpointsSlotKey = encoding.getSlotKey(account, numCheckpointsSlotIndex); | ||
const numCheckpointsSlotKeyU256 = splitUint256.SplitUint256.fromHex(numCheckpointsSlotKey); | ||
|
||
const block = await this.getBlockNumberForTimestamp(timestamp); | ||
const numCheckpoints = await this.contractFor(contractAddress).numCheckpoints(account, { | ||
blockTag: block, | ||
}); | ||
const checkpointToQuery = `0x${(Number(numCheckpoints) - 1).toString(16)}`; | ||
|
||
const checkpointsSlotKey = encoding.getNestedSlotKey([account, checkpointToQuery], checkpointsSlotIndex); | ||
const checkpointsSlotKeyU256 = splitUint256.SplitUint256.fromHex(checkpointsSlotKey); | ||
|
||
const [numCheckpointsProofInputs, checkpointsProofInputs] = await Promise.all([ | ||
this.fetchProofInputs(contractAddress, numCheckpointsSlotKey, block), | ||
this.fetchProofInputs(contractAddress, checkpointsSlotKey, block), | ||
]); | ||
|
||
const numCheckpointsUserParams = [ | ||
// Storage Key (u256) | ||
numCheckpointsSlotKeyU256.low, | ||
numCheckpointsSlotKeyU256.high, | ||
// Storage Proof | ||
`0x${numCheckpointsProofInputs.storageProofSubArrayLength.toString(16)}`, | ||
...numCheckpointsProofInputs.storageProof, | ||
]; | ||
const checkpointsUserParams = [ | ||
// Storage Key (u256) | ||
checkpointsSlotKeyU256.low, | ||
checkpointsSlotKeyU256.high, | ||
// Storage Proof | ||
`0x${checkpointsProofInputs.storageProofSubArrayLength.toString(16)}`, | ||
...checkpointsProofInputs.storageProof, | ||
]; | ||
|
||
|
||
return [ | ||
`0x${numCheckpointsUserParams.length.toString(16)}`, | ||
...numCheckpointsUserParams, | ||
`0x${checkpointsUserParams.length.toString(16)}`, | ||
...checkpointsUserParams, | ||
] | ||
} | ||
|
||
public async getStrategyPreCalls( | ||
account: string, | ||
timestamp: string, | ||
strategyId: string, | ||
): Promise<Call[]> { | ||
const strategy = await this.getStrategyAddressAndParams(strategyId); | ||
const [contractAddress, numCheckpointsSlotIndex] = strategy.params; | ||
|
||
// Only the account proof is used, so it's okay to only query with the first slot key. | ||
const slotKey = encoding.getSlotKey(account, numCheckpointsSlotIndex); | ||
|
||
const block = await this.getBlockNumberForTimestamp(timestamp); | ||
const storageHash = await this.getStorageHash(contractAddress, block); | ||
|
||
// We only need to prove the account if the storage hash hasn't been populated. | ||
if (storageHash.isZero()) { | ||
const [proofInputs, processBlockInputs] = await Promise.all([ | ||
this.fetchProofInputs(contractAddress, slotKey, block), | ||
storageProofs.getProcessBlockInputsForBlockNumber( | ||
this.provider, | ||
block, | ||
this._evmChainId, | ||
), | ||
]); | ||
return [ | ||
{ | ||
contractAddress: this._addresses.starknet.herodotus.factRegistry, | ||
entrypoint: 'prove_account', | ||
calldata: [ | ||
// Account Fields | ||
1, | ||
AccountField.StorageHash, | ||
// Block Header RLP | ||
processBlockInputs.headerInts.length, | ||
...processBlockInputs.headerInts, | ||
// Account | ||
contractAddress, | ||
// Proof | ||
proofInputs.accountProofSubArrayLength, | ||
...proofInputs.accountProof, | ||
], | ||
}, | ||
]; | ||
} | ||
return []; | ||
} | ||
|
||
/** | ||
* Get the total governance power for the provided config | ||
* @param config The governance power strategy config information | ||
*/ | ||
public async getPower(config: GovPowerConfig): Promise<BigNumber> { | ||
const block = await this.getBlockNumberForTimestamp(config.timestamp); | ||
const token = BigNumber.from(config.params[0]).toHexString(); | ||
const balance = await this.contractFor(token).getCurrentVotes(config.user, { | ||
blockTag: block, | ||
}); | ||
return balance.mul(config.params?.[3] ?? 1); | ||
} | ||
|
||
/** | ||
* Returns a contract instance for the provided token address | ||
* @param token The token address | ||
*/ | ||
private contractFor(token: string) { | ||
return new Contract(token, [GET_CURRENT_VOTES_FUNC, NUM_CHECKPOINTS_FUNC], this._evm); | ||
} | ||
} |
Oops, something went wrong.