Skip to content

Commit

Permalink
Merge pull request #59 from razor-network/sameer/raz-619-update-bridg…
Browse files Browse the repository at this point in the history
…e-proxy-contracts-supporting-merkle

feat: merkle verification and changes implemented
  • Loading branch information
SamAg19 authored Dec 19, 2023
2 parents f9882b6 + 45493e6 commit 8967987
Show file tree
Hide file tree
Showing 10 changed files with 1,558 additions and 349 deletions.
112 changes: 92 additions & 20 deletions contracts/Forwarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,20 @@ contract Forwarder is AccessControlEnumerable, Pausable {
bytes32 public constant PAUSE_ROLE = keccak256("PAUSE_ROLE");

address public resultManager;
mapping(bytes32 => bytes) public collectionPayload;
bytes4 public resultGetterSelector;
bytes4 public updateSelector;
bytes4 public validateSelector;

event PermissionSet(address sender);
event PermissionRemoved(address sender);

error NoSelectorPresent();

modifier checkSelector(bytes4 selector) {
if(selector == bytes4(0)) revert NoSelectorPresent();
_;
}

constructor(address _resultManager) {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
resultManager = _resultManager;
Expand All @@ -32,44 +41,107 @@ contract Forwarder is AccessControlEnumerable, Pausable {
external
onlyRole(FORWARDER_ADMIN_ROLE)
{
require(_resultManager.isContract(), "Not a contract address");
resultManager = _resultManager;
}

/// @notice Set collection payload
/// @dev Allows admin to set collection payload
/// @param _collectionName keccak256 hash of collection name
/// @param _payload payload to call
function setCollectionPayload(
bytes32 _collectionName,
bytes memory _payload
/// @notice Set resultGetter Selector
/// @dev Allows admin to set resultGetter Selector
/// @param _resultGetterSelector resultGetter Selector
function setResultGetterSelector(
bytes4 _resultGetterSelector
) external onlyRole(FORWARDER_ADMIN_ROLE) {
collectionPayload[_collectionName] = _payload;
resultGetterSelector = _resultGetterSelector;
}

/// @notice Set update selector
/// @dev Allows admin to set update selector
/// @param _updateSelector update selector
function setUpdateSelector(bytes4 _updateSelector)
external
onlyRole(FORWARDER_ADMIN_ROLE)
{
updateSelector = _updateSelector;
}

/// @notice Set validate selector
/// @dev Allows admin to set validate selector
/// @param _validateSelector validate selector
function setValidateSelector(bytes4 _validateSelector)
external
onlyRole(FORWARDER_ADMIN_ROLE)
{
validateSelector = _validateSelector;
}

/// @notice pause the contract
function pause() external onlyRole(PAUSE_ROLE) {
Pausable._pause();
}

/// @notice unpause the contract
function unpause() external onlyRole(PAUSE_ROLE) {
Pausable._unpause();
}

/// @notice get result by collection name
function getResult(bytes32 collectionName)
/**
* @notice Updates the result based on the provided data and returns the latest result
* @param data bytes data required to update the result
* @return result of the collection, its power and timestamp
*/
function fetchResult(bytes calldata data)
external
view
whenNotPaused
checkSelector(updateSelector)
onlyRole(TRANSPARENT_FORWARDER_ROLE)
returns (uint256, int8)
returns (uint256, int8, uint256)
{
require(
collectionPayload[collectionName].length > 0,
"Invalid collection name"
bytes memory returnData = resultManager.functionCall(
abi.encodePacked(
updateSelector,
data
)
);
bytes memory data = resultManager.functionStaticCall(
collectionPayload[collectionName]
return abi.decode(returnData, (uint256, int8, uint256));
}

/**
* @dev using the hash of collection name, clients can query the result of that collection
* @param name bytes32 hash of the collection name
* @return result of the collection and its power
*/
function getResult(bytes32 name)
external
view
whenNotPaused
checkSelector(resultGetterSelector)
onlyRole(TRANSPARENT_FORWARDER_ROLE)
returns (uint256, int8, uint256)
{
bytes memory returnData = resultManager.functionStaticCall(
abi.encodePacked(
resultGetterSelector,
name
)
);
return abi.decode(data, (uint256, int8));
return abi.decode(returnData, (uint256, int8, uint256));
}

/**
* @dev validates the result based on the provided data and returns the validity
* @param data bytes data required to validate the result
* @return validity of the result
*/
function validateResult(bytes calldata data)
external
view
whenNotPaused
checkSelector(validateSelector)
onlyRole(TRANSPARENT_FORWARDER_ROLE)
returns (bool)
{
bytes memory returnData = resultManager.functionStaticCall(
abi.encodePacked(validateSelector, data)
);
return abi.decode(returnData, (bool));
}
}
128 changes: 85 additions & 43 deletions contracts/ResultManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,84 +3,123 @@ pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract ResultManager is AccessControlEnumerable {
struct Block {
bytes message; // epoch, timestamp, Value[]
bytes signature;
}

struct Value {
int8 power;
uint16 collectionId;
bytes32 name;
uint256 value;
uint256 lastUpdatedTimestamp;
}

bytes32 public constant RESULT_MANAGER_ADMIN_ROLE =
keccak256("RESULT_MANAGER_ADMIN_ROLE");
bytes32 public constant FORWARDER_ROLE = keccak256("FORWARDER_ROLE");

address public signerAddress;
uint256 public lastUpdatedTimestamp;
uint32 public latestEpoch;

/// @notice mapping for name of collection in bytes32 -> collectionid
mapping(bytes32 => uint16) public collectionIds;

/// @notice mapping for CollectionID -> Value Info
mapping(uint16 => Value) private _collectionResults;

event BlockReceived(Block messageBlock);
mapping(bytes32 => Value) private _collectionResults;

event SignerUpdated(
address sender,
address indexed prevSigner,
address indexed newSigner
);

event ResultUpdated(Value value);

error InvalidSignature();
error InvalidMerkleProof();

constructor(address _signerAddress) {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
signerAddress = _signerAddress;
}

/**
* @notice Updates the signer address
*/
function updateSignerAddress(
address _signerAddress
) external onlyRole(RESULT_MANAGER_ADMIN_ROLE) {
emit SignerUpdated(msg.sender, signerAddress, _signerAddress);
signerAddress = _signerAddress;
}

/** @notice Updates the result based on the provided Merkle proof and decoded result. Regardless of whether the result
* is updated, a result will be returned.
* @param merkleRoot The root of the Merkle tree
* @param proof The Merkle proof for the result
* @param result The decoded result
* @param signature The signature for the result
* @return result of the collection, its power and timestamp
*/
function updateResult(
bytes32 merkleRoot,
bytes32[] memory proof,
Value memory result,
bytes memory signature
) external onlyRole(FORWARDER_ROLE) returns (uint256, int8, uint256) {
if (result.lastUpdatedTimestamp > _collectionResults[result.name].lastUpdatedTimestamp) {
bytes memory resultBytes = abi.encode(result);
bytes32 messageHash = keccak256(
abi.encodePacked(merkleRoot, resultBytes)
);
if(
ECDSA.recover(
ECDSA.toEthSignedMessageHash(messageHash),
signature
) != signerAddress) revert InvalidSignature();

bytes32 leaf = keccak256(
bytes.concat(keccak256(abi.encode(resultBytes)))
);
if(
!MerkleProof.verify(proof, merkleRoot, leaf)
) revert InvalidMerkleProof();
_collectionResults[result.name] = result;

emit ResultUpdated(result);
}

return _getResult(result.name);
}

/**
* @dev Verify the signature and update the results
* Requirements:
*
* - ecrecover(signature) should match with signerAddress
* @dev validates the result based on the provided data and returns the validity
* @param merkleRoot The root of the Merkle tree
* @param proof The Merkle proof for the result
* @param result The decoded result
* @param signature The signature for the result
* @return validity of the result
*/
function setBlock(Block memory messageBlock) external {
(uint32 epoch, uint256 timestamp, Value[] memory values) = abi.decode(
messageBlock.message,
(uint32, uint256, Value[])
function validateResult(
bytes32 merkleRoot,
bytes32[] memory proof,
Value memory result,
bytes memory signature
) external view onlyRole(FORWARDER_ROLE) returns (bool) {
bytes memory resultBytes = abi.encode(result);
bytes32 messageHash = keccak256(
abi.encodePacked(merkleRoot, resultBytes)
);
require(epoch > latestEpoch, "epoch must be > latestEpoch");

bytes32 messageHash = keccak256(messageBlock.message);
require(
if (
ECDSA.recover(
ECDSA.toEthSignedMessageHash(messageHash),
messageBlock.signature
) == signerAddress,
"invalid signature"
);
signature
) != signerAddress
) return false;

for (uint256 i; i < values.length; i++) {
_collectionResults[values[i].collectionId] = values[i];
collectionIds[values[i].name] = values[i].collectionId;
}
lastUpdatedTimestamp = timestamp;
latestEpoch = epoch;
bytes32 leaf = keccak256(
bytes.concat(keccak256(abi.encode(resultBytes)))
);
if (!MerkleProof.verify(proof, merkleRoot, leaf)) return false;

emit BlockReceived(messageBlock);
return true;
}

/**
Expand All @@ -90,17 +129,20 @@ contract ResultManager is AccessControlEnumerable {
*/
function getResult(
bytes32 _name
) external view onlyRole(FORWARDER_ROLE) returns (uint256, int8) {
uint16 id = collectionIds[_name];
return _getResultFromID(id);
) external view onlyRole(FORWARDER_ROLE) returns (uint256, int8, uint256) {
return _getResult(_name);
}

/**
* @dev Returns collection result and power with collectionId as parameter
* @dev internal function where using the hash of collection name, clients can query the
* result of that collection
* @param _name bytes32 hash of the collection name
* @return result of the collection and its power
*/
function _getResultFromID(
uint16 _id
) internal view returns (uint256, int8) {
return (_collectionResults[_id].value, _collectionResults[_id].power);
function _getResult(
bytes32 _name
) internal view returns (uint256, int8, uint256) {
Value memory result = _collectionResults[_name];
return (result.value, result.power, result.lastUpdatedTimestamp);
}
}
Loading

0 comments on commit 8967987

Please sign in to comment.