Skip to content

Commit

Permalink
Merge pull request #160 from futureversecom/SM-117-Seeker-Oracle
Browse files Browse the repository at this point in the history
SM-117 Seeker Oracle
  • Loading branch information
teinnt authored Jan 16, 2024
2 parents 334b827 + 94af5c5 commit dfaca97
Show file tree
Hide file tree
Showing 8 changed files with 477 additions and 18 deletions.
28 changes: 20 additions & 8 deletions .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,17 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: restore cache
uses: actions/cache@v3
- name: checkout code
uses: actions/checkout@v3

- name: Use Node.js 18.14.1
uses: actions/setup-node@v3
with:
path: ./
key: repo-${{hashFiles('./')}}
node-version: 18.14.1
cache: 'yarn'

- name: yarn install
run: yarn

- name: test contracts
run: ITERATIONS=10000 npm test
Expand All @@ -66,11 +72,17 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: restore cache
uses: actions/cache@v3
- name: checkout code
uses: actions/checkout@v3

- name: Use Node.js 18.14.1
uses: actions/setup-node@v3
with:
path: ./
key: repo-${{hashFiles('./')}}
node-version: 18.14.1
cache: 'yarn'

- name: yarn install
run: yarn

- name: test contracts
run: yarn coverage
Expand Down
1 change: 1 addition & 0 deletions common/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type SyloContracts = {
rewardsManager: factories.contracts.payments.ticketing.RewardsManager;
directory: factories.contracts.staking.Directory;
syloTicketing: factories.contracts.payments.SyloTicketing;
seekerPowerOracle: factories.contracts.SeekerPowerOracle;
seekers: factories.contracts.mocks.TestSeekers;
futurepassRegistrar: factories.contracts.mocks.TestFuturepassRegistrar;
};
Expand Down
133 changes: 133 additions & 0 deletions contracts/SeekerPowerOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.18;

import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

import "./interfaces/ISeekerPowerOracle.sol";

/**
* @notice Acts as a source of information for Seeker Powers. Allows setting
* a Seeker's power level via a restricted oracle account call. Seeker Power can also
* be set by any account if the correct Oracle signature proof is provided.
*/
contract SeekerPowerOracle is ISeekerPowerOracle, Initializable, Ownable2StepUpgradeable {
/**
* @notice The oracle account. This contract accepts any attestations of
* Seeker power that have been signed by this account.
*/
address public oracle;

/**
* @notice Tracks nonce used when register the Seeker power to
* prevent signature re-use.
*/
mapping(bytes32 => address) private proofNonces;

/**
* @notice Tracks the set of Seeker Power levels.
*/
mapping(uint256 => uint256) public seekerPowers;

event SeekerPowerUpdated(uint256 indexed seekerId, uint256 indexed power);

error UnauthorizedRegisterSeekerPowerCall();
error NonceCannotBeReused();
error PowerCannotBeZero();

function initialize(address _oracle) external initializer {
Ownable2StepUpgradeable.__Ownable2Step_init();

oracle = _oracle;
}

/**
* @notice Sets the oracle account.
* @param _oracle The oracle account.
*/
function setOracle(address _oracle) external onlyOwner {
oracle = _oracle;
}

/**
* @notice Registers a Seeker's power level. Only callable by the
* owner or the oracle account.
* @param seekerId The id of the Seeker.
* @param power The power level of the Seeker.
*/
function registerSeekerPowerRestricted(uint256 seekerId, uint256 power) external {
if (msg.sender != oracle) {
revert UnauthorizedRegisterSeekerPowerCall();
}

if (power == 0) {
revert PowerCannotBeZero();
}

seekerPowers[seekerId] = power;
emit SeekerPowerUpdated(seekerId, power);
}

/**
* @notice Registers a Seeker's power level. Callable by any account
* but requires a proof signed by the oracle.
* @param seekerId The id of the Seeker.
* @param power The power level of the Seeker.
*/
function registerSeekerPower(
uint256 seekerId,
uint256 power,
bytes32 nonce,
bytes calldata proof
) external {
if (proofNonces[nonce] != address(0)) {
revert NonceCannotBeReused();
}

if (power == 0) {
revert PowerCannotBeZero();
}

bytes memory proofMessage = getProofMessage(seekerId, power, nonce);
bytes32 ecdsaHash = ECDSA.toEthSignedMessageHash(proofMessage);

if (ECDSA.recover(ecdsaHash, proof) != oracle) {
revert UnauthorizedRegisterSeekerPowerCall();
}

seekerPowers[seekerId] = power;
proofNonces[nonce] = oracle;

emit SeekerPowerUpdated(seekerId, power);
}

/**
* @notice Retrieves a Seeker's stored power level.
* @param seekerId The id of the Seeker.
*/
function getSeekerPower(uint256 seekerId) external view returns (uint256) {
return seekerPowers[seekerId];
}

/**
* @notice Constructs a proof message for the oracle to sign.
* @param seekerId The id of the Seeker.
* @param power The power level of the Seeker.
*/
function getProofMessage(
uint256 seekerId,
uint256 power,
bytes32 nonce
) public pure returns (bytes memory) {
return
abi.encodePacked(
Strings.toString(seekerId),
":",
Strings.toString(power),
":",
Strings.toHexString(uint256(nonce), 32)
);
}
}
1 change: 1 addition & 0 deletions contracts/interfaces/IFuturePassRegistrar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ pragma solidity ^0.8.18;

interface IFuturePassRegistrar {
function futurepassOf(address owner) external view returns (address);

function create(address owner) external returns (address);
}
23 changes: 23 additions & 0 deletions contracts/interfaces/ISeekerPowerOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.18;

interface ISeekerPowerOracle {
function setOracle(address oracle) external;

function registerSeekerPowerRestricted(uint256 seekerId, uint256 power) external;

function registerSeekerPower(
uint256 seekerId,
uint256 power,
bytes32 nonce,
bytes calldata proof
) external;

function getSeekerPower(uint256 seekerId) external view returns (uint256);

function getProofMessage(
uint256 seekerId,
uint256 power,
bytes32 nonce
) external pure returns (bytes memory);
}
20 changes: 10 additions & 10 deletions contracts/mocks/TestFuturepassRegistrar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
pragma solidity ^0.8.18;

contract TestFuturepassRegistrar {
mapping(address => address) registrations;
mapping(address => address) registrations;

function futurepassOf(address owner) external view returns (address) {
return registrations[owner];
}
function futurepassOf(address owner) external view returns (address) {
return registrations[owner];
}

function create(address owner) external returns (address) {
// ticketing contract does not actually care about futurepass
// address value, just needs to be non-zero
registrations[owner] = owner;
return owner;
}
function create(address owner) external returns (address) {
// ticketing contract does not actually care about futurepass
// address value, just needs to be non-zero
registrations[owner] = owner;
return owner;
}
}
Loading

0 comments on commit dfaca97

Please sign in to comment.