Skip to content

Commit

Permalink
feat(IntentSource): efficient reward claim
Browse files Browse the repository at this point in the history
  • Loading branch information
re1ro committed Jan 7, 2025
1 parent 1885aca commit f01475e
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 101 deletions.
7 changes: 4 additions & 3 deletions contracts/HyperProver.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import '@hyperlane-xyz/core/contracts/interfaces/IMessageRecipient.sol';
import "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol";
import './interfaces/SimpleProver.sol';
import {IMessageRecipient} from '@hyperlane-xyz/core/contracts/interfaces/IMessageRecipient.sol';
import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol";
import {SimpleProver} from './interfaces/SimpleProver.sol';
import {Semver} from "./libs/Semver.sol";


contract HyperProver is IMessageRecipient, SimpleProver {
Expand Down
16 changes: 7 additions & 9 deletions contracts/Inbox.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "./interfaces/IInbox.sol";
import "./types/Intent.sol";
import "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol";
import "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import {IMailbox, IPostDispatchHook} from "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol";
import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IInbox} from "./interfaces/IInbox.sol";
import {Intent, Route, Call} from "./types/Intent.sol";
import {Semver} from "./libs/Semver.sol";

/**
* @title Inbox
Expand Down Expand Up @@ -125,7 +126,7 @@ contract Inbox is IInbox, Ownable {
bytes memory messageBody = abi.encode(hashes, claimants);
bytes32 _prover32 = _prover.addressToBytes32();

emit HyperInstantFulfillment(_expectedHash, _sourceChainID, _claimant);
emit HyperInstantFulfillment(_expectedHash, _route.source, _claimant);

uint256 fee = fetchFee(_route.source, _prover32, messageBody, _metadata, _postDispatchHook);
if (msg.value < fee) {
Expand Down Expand Up @@ -170,9 +171,6 @@ contract Inbox is IInbox, Ownable {

bytes[] memory results = _fulfill( _route, _rewardHash, _claimant, _expectedHash);

bytes[] memory results =
_fulfill(_sourceChainID, _targets, _data, _expiryTime, _nonce, _claimant, _expectedHash);

return results;
}

Expand Down
163 changes: 87 additions & 76 deletions contracts/IntentSource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "./interfaces/IIntentSource.sol";
import "./interfaces/SimpleProver.sol";
import "./types/Intent.sol";
import {IIntentSource} from "./interfaces/IIntentSource.sol";
import {SimpleProver} from "./interfaces/SimpleProver.sol";
import {Intent, Reward, Route} from "./types/Intent.sol";
import {Semver} from "./libs/Semver.sol";

import "./IntentVault.sol";import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IntentVault} from "./IntentVault.sol";

/**
* This contract is the source chain portion of the Eco Protocol's intent system.
Expand All @@ -22,9 +23,8 @@ contract IntentSource is IIntentSource {
using SafeERC20 for IERC20;

// stores the intents
mapping(bytes32 intentHash => bool) public claimed;
mapping(bytes32 intentHash => address) public claimed;

address public vaultClaimant;
address public vaultRefundToken;

/**
Expand All @@ -37,40 +37,27 @@ contract IntentSource is IIntentSource {
return Semver.version();
}

function getVaultClaimant() external view returns (address) {
return vaultClaimant;
}

function getVaultRefundToken() external view returns (address) {
return vaultRefundToken;
function getIntentHash(
Intent calldata intent
) public pure returns (bytes32 intentHash, bytes32 routeHash, bytes32 rewardHash) {
routeHash = keccak256(abi.encode(intent.route));
rewardHash = keccak256(abi.encode(intent.reward));
intentHash = keccak256(abi.encodePacked(routeHash, rewardHash));
}

function intentVaultAddress(
Intent calldata intent
) public view returns (address) {
/* Convert a hash which is bytes32 to an address which is 20-byte long
according to https://docs.soliditylang.org/en/v0.8.9/control-structures.html?highlight=create2#salted-contract-creations-create2 */
return
address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
address(this),
keccak256(abi.encode(intent.route)),
// Encoding delegateData and refundAddress as constructor params
keccak256(
abi.encodePacked(
type(IntentVault).creationCode,
abi.encode(intent.reward)
)
)
)
)
)
)
);
(bytes32 intentHash, bytes32 routeHash,) = getIntentHash(intent);
return _getIntentVaultAddress(intentHash, routeHash, intent.reward);
}

function getClaimed(bytes32 intentHash) external view returns (address) {
return claimed[intentHash];
}

function getVaultRefundToken() external view returns (address) {
return vaultRefundToken;
}

/**
Expand All @@ -85,13 +72,11 @@ contract IntentSource is IIntentSource {
Reward calldata reward = intent.reward;

uint256 rewardsLength = reward.tokens.length;
bytes32 routeHash;

uint256 chainID = block.chainid;
bytes32 routeHash = keccak256(abi.encode(route));
bytes32 rewardHash = keccak256(abi.encode(reward));
intentHash = keccak256(abi.encodePacked(routeHash, rewardHash));
(intentHash, routeHash,) = getIntentHash(intent);

address vault = intentVaultAddress(intent);
address vault = _getIntentVaultAddress(intentHash, routeHash, reward);

if (addRewards) {
if (reward.nativeValue > 0) {
Expand All @@ -110,7 +95,7 @@ contract IntentSource is IIntentSource {

IERC20(token).safeTransferFrom(msg.sender, vault, amount);
}
} else {
} else if (block.chainid == intent.route.source) {
require(_validateIntent(intent, vault), "IntentSource: invalid intent");
}

Expand All @@ -131,35 +116,12 @@ contract IntentSource is IIntentSource {
function validateIntent(
Intent calldata intent
) external view returns (bool) {
address vault = intentVaultAddress(intent);
(bytes32 intentHash, bytes32 routeHash,) = getIntentHash(intent);
address vault = _getIntentVaultAddress(intentHash, routeHash, intent.reward);

return _validateIntent(intent, vault);
}

function _validateIntent(
Intent calldata intent,
address vault
) internal view returns (bool) {
Reward calldata reward = intent.reward;
uint256 rewardsLength = reward.tokens.length;

if (reward.expiryTime < block.timestamp + MINIMUM_DURATION / 2) {
return false;
}

if (vault.balance < reward.nativeValue) return false;

for (uint256 i = 0; i < rewardsLength; i++) {
address token = reward.tokens[i].token;
uint256 amount = reward.tokens[i].amount;
uint256 balance = IERC20(token).balanceOf(vault);

if (balance < amount) return false;
}

return true;
}

/**
* @notice Withdraws the rewards associated with an intent to its claimant
* @param routeHash The hash of the route of the intent
Expand All @@ -174,15 +136,13 @@ contract IntentSource is IIntentSource {
);

// Claim the rewards if the intent has not been claimed
if (claimant != address(0) && !claimed[intentHash]) {
vaultClaimant = claimant;
claimed[intentHash] = true;
if (claimant != address(0) && claimed[intentHash] == address(0)) {
claimed[intentHash] = claimant;

emit Withdrawal(intentHash, claimant);

new IntentVault{salt: routeHash}(reward);
new IntentVault{salt: routeHash}(intentHash, reward);

vaultClaimant = address(0);
return;
}

Expand All @@ -193,7 +153,7 @@ contract IntentSource is IIntentSource {

emit Withdrawal(intentHash, reward.creator);

new IntentVault{salt: routeHash}(reward);
new IntentVault{salt: routeHash}(intentHash, reward);
}

/**
Expand All @@ -215,14 +175,65 @@ contract IntentSource is IIntentSource {
bytes32 rewardHash = keccak256(abi.encode(reward));
bytes32 intentHash = keccak256(abi.encodePacked(routeHash, rewardHash));

if (!claimed[intentHash] || block.timestamp < reward.expiryTime) {
if (claimed[intentHash] == address(0) && block.timestamp < reward.expiryTime) {
revert UnauthorizedWithdrawal(intentHash);
}

vaultRefundToken = token;

new IntentVault{salt: routeHash}(reward);
new IntentVault{salt: routeHash}(intentHash,reward);

vaultRefundToken = address(0);
}

function _validateIntent(
Intent calldata intent,
address vault
) internal view returns (bool) {
Reward calldata reward = intent.reward;
uint256 rewardsLength = reward.tokens.length;

if (vault.balance < reward.nativeValue) return false;

for (uint256 i = 0; i < rewardsLength; i++) {
address token = reward.tokens[i].token;
uint256 amount = reward.tokens[i].amount;
uint256 balance = IERC20(token).balanceOf(vault);

if (balance < amount) return false;
}

return true;
}


function _getIntentVaultAddress(
bytes32 intentHash,
bytes32 routeHash,
Reward calldata reward
) internal view returns (address) {
/* Convert a hash which is bytes32 to an address which is 20-byte long
according to https://docs.soliditylang.org/en/v0.8.9/control-structures.html?highlight=create2#salted-contract-creations-create2 */
return
address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
address(this),
routeHash,
keccak256(
abi.encodePacked(
type(IntentVault).creationCode,
abi.encode(intentHash, reward)
)
)
)
)
)
)
);
}

}
7 changes: 3 additions & 4 deletions contracts/IntentVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import "./types/Intent.sol";
contract IntentVault {
using SafeERC20 for IERC20;

constructor(Reward memory reward) {
constructor(bytes32 intentHash, Reward memory reward) payable {
uint256 rewardsLength = reward.tokens.length;

address claimant = IIntentSource(msg.sender).getVaultClaimant();
address refundToken;
address claimant = IIntentSource(msg.sender).getClaimed(intentHash);
address refundToken = IIntentSource(msg.sender).getVaultRefundToken();

if (claimant == address(0)) {
claimant = reward.creator;
refundToken = IIntentSource(msg.sender).getVaultRefundToken();
}

for (uint256 i; i < rewardsLength; ++i) {
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IInbox.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Semver, ISemver} from "../libs/Semver.sol";
import {ISemver} from "../libs/Semver.sol";

import {Route} from "../types/Intent.sol";

Expand Down
4 changes: 2 additions & 2 deletions contracts/interfaces/IIntentSource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {Semver, ISemver} from "./ISemver.sol";
import {ISemver} from "../libs/Semver.sol";

import {Call, TokenAmount, Reward, Intent} from "../types/Intent.sol";

Expand Down Expand Up @@ -84,7 +84,7 @@ interface IIntentSource is ISemver {
*/
event Withdrawal(bytes32 _hash, address indexed _recipient);

function getVaultClaimant() external view returns (address);
function getClaimed(bytes32 intentHash) external view returns (address);

function getVaultRefundToken() external view returns (address);

Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IL1Block.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Semver, ISemver} from "../libs/Semver.sol";
import {ISemver} from "../libs/Semver.sol";

interface IL1Block is ISemver{
/// @notice The latest L1 block number known by the L2 system.
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IProver.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Semver, ISemver} from "../libs/Semver.sol";
import {ISemver} from "../libs/Semver.sol";

interface IProver is ISemver {
// The types of proof that provers can be
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/SimpleProver.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "./IProver.sol";
import {IProver} from "./IProver.sol";

abstract contract SimpleProver is IProver {
/**
Expand Down
Loading

0 comments on commit f01475e

Please sign in to comment.