Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e57c883
feat: add support for syncing of votes
GitGuru7 Jan 31, 2024
4d3b103
refactor: add admin contract and function to get accumulated votes
GitGuru7 Feb 12, 2024
7555888
feat: add tests
GitGuru7 Feb 12, 2024
af8025d
feat: add deployment scripts and json files
GitGuru7 Feb 13, 2024
1b3f0de
feat: updating deployment files
GitGuru7 Feb 13, 2024
8e3d368
fix: storage of destination vault
GitGuru7 Feb 14, 2024
5fbb0fa
chore: organise directories properly
GitGuru7 Feb 14, 2024
aca1b3b
feat: prevent retrying for unsupported trusted remote
GitGuru7 Feb 14, 2024
aca431f
refactor: variables name and add indexed events
GitGuru7 Feb 14, 2024
9747500
refactor: add CEI pattern
GitGuru7 Feb 14, 2024
24a4cba
fix: indexing of checkpoints
GitGuru7 Feb 14, 2024
a46f985
refactor: deployment script and add deployment files
GitGuru7 Feb 19, 2024
99100b9
fix: use specific imports and add SPDX license
GitGuru7 Feb 20, 2024
913f59e
feat: add XVSVaultDest deployments of sepolia
GitGuru7 Feb 20, 2024
1158abd
refactor: transfer ownership in deployment script and update deployments
GitGuru7 Feb 21, 2024
d4d6ad0
refactor: replace onlyOwner with acm in setTrustedRemoteAddress
GitGuru7 Feb 21, 2024
7ba9389
refactor: eliminate checkpoint without chain id
GitGuru7 Feb 28, 2024
5795d1e
refactor: remove index_ from removechainId()
GitGuru7 Feb 28, 2024
2964336
feat: add functionality to sync votes externally in XVSVaultDest
GitGuru7 Feb 28, 2024
7453e40
refactor: dynamically assign 'useZero' parameter from user input
GitGuru7 Feb 29, 2024
68fd091
refactor: update bridge access permission to use access control for e…
GitGuru7 Mar 1, 2024
0752c3b
refactor: update condition to sync only latest votes
GitGuru7 Mar 1, 2024
a5df8ca
feat: update getPriorVotes() function
GitGuru7 Mar 1, 2024
9ab7b05
feat: remove blocking behaviour of bridge
GitGuru7 Mar 5, 2024
4094a9d
feat: extend sender contract with layer zero default functionalities …
GitGuru7 Mar 5, 2024
3bf6bea
feat: allowing single bridging in a transaction
GitGuru7 Mar 5, 2024
88a63b1
feat: add pausable functionality in src and dest bridge
GitGuru7 Mar 5, 2024
be122f2
feat: refund unused gas back to the user
GitGuru7 Mar 5, 2024
4fd2910
feat: add admin contract for sender too and fix tests
GitGuru7 Mar 6, 2024
02d0594
Merge branch 'develop' into feat/sync-votes
GitGuru7 Mar 7, 2024
5a3590d
feat: updating deployment files
GitGuru7 Mar 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 220 additions & 0 deletions contracts/XVSVault/VoteSyncBridgeUtils/MultichainVoteRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.13;

import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
import { IXVSVault } from "./interfaces/IXVSVault.sol";
import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contracts/validators.sol";

/**
* @title MultichainVoteRegistry
* @author Venus
* @notice The MultichainVoteRegistry contract is access controlled which keeps the count of the votes on the basis of chainId.
* This contract is responsible for providing the final accumulated voting power (on all supported chains) to the governanceDelegate contract when voting on a proposal.
* It is invoked by layerZero bridge receiver contract to update the vote state/checkpoint for accounts that have staked on chains apart from BNB.
*/
contract MultichainVoteRegistry is AccessControlledV8 {
/**
* @notice A checkpoint for marking number of votes from a given block
*/
struct Checkpoint {
uint32 fromBlock;
uint96 votes;
}
/**
* @notice LZ chain Id for all supported networks
*/
uint16[] public lzChainIds;

/**
* @notice Address of XVSVault deployed on BNB chain
*/
IXVSVault public immutable XVSVault;
/**
* @notice The number of checkpoints for each account for each chain id
*/
mapping(uint16 => mapping(address => uint32)) public numCheckpointsWithChainId;

/**
* @notice A record of votes checkpoints for each account, by chain id and index
*/
mapping(uint16 => mapping(address => mapping(uint32 => Checkpoint))) public checkpointsWithChainId;

/**
* @notice Emitted when user's votes updated
*/
event DestVotesUpdated(
uint16 indexed chainId,
address indexed delegatee,
uint32 checkpoints,
uint32 blockNumber,
uint96 votes,
uint32 nCheckpoint
);
/**
* @notice Emitted when new chain Id is added to supported chain id list
*/
event AddChainId(uint16 indexed chainId);
/**
* @notice Emitted when chain Id is removed from supported chain is list
*/
event RemoveChainId(uint16 indexed chainId);

/// @custom:oz-upgrades-unsafe-allow constructor
constructor(IXVSVault XVSVault_) {
ensureNonzeroAddress(address(XVSVault_));
XVSVault = XVSVault_;
}

/**
* @notice Initialize the contract
* @param accessControlManager_ Address of access control manager
*/
function initialize(address accessControlManager_) external initializer {
ensureNonzeroAddress(accessControlManager_);
__AccessControlled_init(accessControlManager_);
}

/**
* @notice Add chainId to supported layer zero chain ids
* @param chainId_ Chain Id i.e. to be added
* @custom:access Controlled by Access Control Manager
* @custom:event Emit AddChainId with chain Id
*/
function addChainId(uint16 chainId_) external {
_checkAccessAllowed("addChainId(uint16)");
require(chainId_ != 0, "MultichainVoteRegistry::addChainId: invalid chain Id");
lzChainIds.push(chainId_);
emit AddChainId(chainId_);
}

/**
*@notice Remove chain Id from supported layer zero chain ids
* @param chainId_ Chain Id i.e. to be removed
* @custom:access Controlled by Access Control Manager
* @custom:event Emit RemoveChainId with chain Id
*/
function removeChainId(uint16 chainId_) external {
_checkAccessAllowed("removeChainId(uint16)");
uint256 length = lzChainIds.length;
uint256 index = length;
for (uint256 i; i < length; ) {
if (lzChainIds[i] == chainId_) {
index = i;
}
unchecked {
i++;
}
}
require(index != length, "MultichainVoteRegistry::removeChainId: chain id not found");

for (uint256 i = index; i < length - 1; ) {
lzChainIds[i] = lzChainIds[i + 1];
unchecked {
i++;
}
}
lzChainIds.pop();

emit RemoveChainId(chainId_);
}

/**
* @notice Synchronizes remote chain votes(amount of XVS stake) for a specific delegatee
* @param chainId_ The Id of the remote chain where votes are being synchronized
* @param delegatee_ The address of the delegatee whose votes are being synchronized
* @param index_ Index for which votes to update
* @param votes_ The total number of votes to be synchronized
* @param nCheckpoint_ The number of checkpoints for each account
* @custom:access Controlled by Access Control Manager
* @custom:event Emit DestVotesUpdated
*/

function syncDestVotes(
uint16 chainId_,
address delegatee_,
uint32 index_,
uint32 nCheckpoint_,
uint96 votes_
) external {
_checkAccessAllowed("syncDestVotes(uint16,address,uint32,uint96,uint32)");
uint32 blockNumber = uint32(block.number);
require(
numCheckpointsWithChainId[chainId_][delegatee_] <= nCheckpoint_,
"MultichainVoteRegistry::syncDestVotes: invalid checkpoint"
);
if (numCheckpointsWithChainId[chainId_][delegatee_] == nCheckpoint_) {
require(
checkpointsWithChainId[chainId_][delegatee_][index_].votes != votes_,
"MultichainVoteRegistry::syncDestVotes: votes already updated"
);
}
Checkpoint memory newCheckpoint = Checkpoint(blockNumber, votes_);
checkpointsWithChainId[chainId_][delegatee_][index_] = newCheckpoint;
numCheckpointsWithChainId[chainId_][delegatee_] = nCheckpoint_;

emit DestVotesUpdated(chainId_, delegatee_, index_, blockNumber, votes_, nCheckpoint_);
}

/**
* @notice Determine the xvs stake balance for an account
* @param account_ The address of the account to check
* @param blockNumber_ The block number to get the vote balance at
* @return The balance that user staked
*/
function getPriorVotes(address account_, uint256 blockNumber_) external view returns (uint96) {
// Fetch votes of user stored in XVSVault on BNB chain
uint96 votesOnBnb = XVSVault.getPriorVotes(account_, blockNumber_);
uint96 totalVotes = votesOnBnb;
uint256 length = lzChainIds.length;

for (uint256 i; i < length; i++) {
totalVotes += getPriorVotesInternal(account_, blockNumber_, lzChainIds[i]);
}
//return accumulated votes
return totalVotes;
}

/**
* @dev Internal function to determine the xvs stake balance for an account
* @param account_ The address of the account to check
* @param blockNumber_ The block number to get the vote balance at
* @param chainId_ Layer zero chain id on which account balance will be checked
* @return The balance that user staked on particular chain id
*/
function getPriorVotesInternal(
address account_,
uint256 blockNumber_,
uint16 chainId_
) internal view returns (uint96) {
uint32 nCheckpoints = numCheckpointsWithChainId[chainId_][account_];
if (nCheckpoints == 0) {
return 0;
}

// First check most recent balance
if (checkpointsWithChainId[chainId_][account_][nCheckpoints - 1].fromBlock <= blockNumber_) {
return checkpointsWithChainId[chainId_][account_][nCheckpoints - 1].votes;
}

// Next check implicit zero balance
if (checkpointsWithChainId[chainId_][account_][0].fromBlock > blockNumber_) {
return 0;
}

uint32 lower = 0;
uint32 upper = nCheckpoints - 1;
while (upper > lower) {
uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
Checkpoint memory cp = checkpointsWithChainId[chainId_][account_][center];
if (cp.fromBlock == blockNumber_) {
return cp.votes;
} else if (cp.fromBlock < blockNumber_) {
lower = center;
} else {
upper = center - 1;
}
}
return checkpointsWithChainId[chainId_][account_][lower].votes;
}
}
139 changes: 139 additions & 0 deletions contracts/XVSVault/VoteSyncBridgeUtils/VotesSyncBridgeAdmin.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.13;

import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
import { ensureNonzeroAddress } from "@venusprotocol/solidity-utilities/contracts/validators.sol";
import { IVoteSyncBridge } from "./interfaces/IVotesSyncBridge.sol";

/**
* @title VotesSyncReceiverAdmin
* @author Venus
* @notice The VotesSyncReceiverAdmin contract extends a parent contract AccessControlledV8 for access control, and it manages contract called voteSyncBridge.
* It maintains a registry of function signatures and names, allowing for dynamic function handling i.e checking of access control of interaction with only owner functions.
*/
contract VotesSyncBridgeAdmin is AccessControlledV8 {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IVoteSyncBridge public immutable voteSyncBridge;
/**
* @notice A mapping keeps track of function signature associated with function name string.
*/
mapping(bytes4 => string) public functionRegistry;

/**
* @notice Event emitted when function registry updated
*/
event FunctionRegistryChanged(string signature, bool active);

/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address voteSyncBridge_) {
ensureNonzeroAddress(voteSyncBridge_);
voteSyncBridge = IVoteSyncBridge(voteSyncBridge_);
_disableInitializers();
}

/**
* @param accessControlManager_ Address of access control manager contract
* @custom:error ZeroAddressNotAllowed is thrown when accessControlManager contract address is zero
*/
function initialize(address accessControlManager_) external initializer {
ensureNonzeroAddress(accessControlManager_);
__AccessControlled_init(accessControlManager_);
}

/**
* @notice Sets the trusted remote address for a specified remote chain ID
* @param remoteChainId Chain Id of the remote chain
* @param remoteAddress Address of the remote bridge
* @custom:error ZeroAddressNotAllowed is thrown when remoteAddress contract address is zero
* @custom:access Controlled by Access Control Manager
*/
function setTrustedRemoteAddress(uint16 remoteChainId, bytes calldata remoteAddress) external {
_checkAccessAllowed("setTrustedRemoteAddress(uint16,bytes)");
require(remoteChainId != 0, "ChainId must not be zero");
ensureNonzeroAddress(bytesToAddress(remoteAddress));
voteSyncBridge.setTrustedRemoteAddress(remoteChainId, remoteAddress);
}

/**
* @notice Returns bool = true if srcAddress is trustedRemote corresponds to chainId_.
* @param remoteChainId Chain Id of the remote chain.
* @param remoteAddress Address of the remote bridge.
* @custom:error ZeroAddressNotAllowed is thrown when remoteAddress contract address is zero.
*/
function isTrustedRemote(uint16 remoteChainId, bytes calldata remoteAddress) external returns (bool) {
require(remoteChainId != 0, "ChainId must not be zero");
ensureNonzeroAddress(bytesToAddress(remoteAddress));
return voteSyncBridge.isTrustedRemote(remoteChainId, remoteAddress);
}

/**
* @notice Invoked when called function does not exist in the contract
* @param data Calldata containing the encoded function call
* @return Result of function call
* @custom:access Controlled by AccessControlManager
*/
fallback(bytes calldata data) external returns (bytes memory) {
string memory fun = _getFunctionName(msg.sig);
require(bytes(fun).length != 0, "Function not found");
_checkAccessAllowed(fun);
(bool ok, bytes memory res) = address(voteSyncBridge).call(data);
require(ok, "call failed");
return res;
}

/**
* @notice A registry of functions that are allowed to be executed from proposals
* @param signatures Function signature to be added or removed
* @param active Bool value, should be true to add function
* @custom:event Emit FunctionRegistryChanged with signatures and its active bool value
* @custom:access Only owner
*/
function upsertSignature(string[] calldata signatures, bool[] calldata active) external onlyOwner {
uint256 signatureLength = signatures.length;
require(signatureLength == active.length, "Input arrays must have the same length");
for (uint256 i; i < signatureLength; i++) {
bytes4 sigHash = bytes4(keccak256(bytes(signatures[i])));
bytes memory signature = bytes(functionRegistry[sigHash]);
if (active[i] && signature.length == 0) {
functionRegistry[sigHash] = signatures[i];
emit FunctionRegistryChanged(signatures[i], true);
} else if (!active[i] && signature.length != 0) {
delete functionRegistry[sigHash];
emit FunctionRegistryChanged(signatures[i], false);
}
}
}

/**
* @notice This function transfer the ownership of the bridge from this contract to new owner
* @param newOwner New owner of the XVS Bridge
* @custom:access Controlled by AccessControlManager
*/
function transferBridgeOwnership(address newOwner) external {
_checkAccessAllowed("transferBridgeOwnership(address)");
ensureNonzeroAddress(newOwner);
voteSyncBridge.transferOwnership(newOwner);
}

/**
* @notice Empty implementation of renounce ownership to avoid any mishappening
*/
function renounceOwnership() public override {}

/**
* @dev Returns function name string associated with function signature
* @param signature Function signature
*/
function _getFunctionName(bytes4 signature) internal view returns (string memory) {
return functionRegistry[signature];
}

/**
* @dev Converts bytes into address
* @param data Data in bytes to be converted into address
*/
function bytesToAddress(bytes calldata data) private pure returns (address) {
return address(uint160(bytes20(data)));
}
}
Loading