-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor: move HC contracts out of submodule and into the main repo. Updates deployer to find the new paths, and to import ABI json from the Forge build rather than compiling in Python. Changes to be committed: modified: Cargo.lock modified: crates/types/contracts/hc_scripts/ExampleDeploy.s.sol modified: crates/types/contracts/hc_scripts/LocalDeploy.s.sol modified: crates/types/contracts/lib/account-abstraction-versions/v0_6 new file: crates/types/contracts/src/hc0_6/HCHelper.sol new file: crates/types/contracts/src/hc0_6/HybridAccount.sol new file: crates/types/contracts/src/hc0_6/HybridAccountFactory.sol new file: crates/types/contracts/src/hc0_6/TestAuctionSystem.sol new file: crates/types/contracts/src/hc0_6/TestCaptcha.sol new file: crates/types/contracts/src/hc0_6/TestHybrid.sol new file: crates/types/contracts/src/hc0_6/TestKyc.sol new file: crates/types/contracts/src/hc0_6/TestRainfallInsurance.sol new file: crates/types/contracts/src/hc0_6/TestSportsBetting.sol new file: crates/types/contracts/src/hc0_6/TestTokenPrice.sol modified: crates/types/contracts/src/v0_6/imports.sol modified: hybrid-compute/deploy-local.py * fix: add a version to web3.py dependency Changes to be committed: modified: hybrid-compute/Dockerfile.offchain-rpc * fix: remove reference to bobanetwork account-abstraction-hc repo. Changes to be committed: modified: .gitmodules * fix: fixes Changes to be committed: modified: hybrid-compute/deploy-local.py modified: hybrid-compute/offchain/userop.py modified: hybrid-compute/offchain/userop_utils.py * fix: change paths to load contract .json Changes to be committed: modified: deploy-local.py * fix: add a pip3 requirements file Changes to be committed: new file: requirements.txt * fix: Revert some dependency updates which caused problems elsewhere. Changes to be committed: modified: Cargo.lock * fix: Update "cargo deny" rules to work with current version of the tool (which deprecated certain keywords). The exceptions should be audited and compared with upstream to ensure they are appropriate. Changes to be committed: modified: deny.toml * fix: unit test for out-of-bounds timestamp Changes to be committed: modified: crates/types/src/timestamp.rs
- Loading branch information
1 parent
4a9778d
commit 9e0afd9
Showing
22 changed files
with
1,074 additions
and
79 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
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
Submodule v0_6
updated
from c79c0f to fa6129
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,198 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.12; | ||
|
||
import "account-abstraction/v0_6/interfaces/INonceManager.sol"; | ||
import "openzeppelin-contracts-versions/v4_9/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
|
||
contract HCHelper { | ||
using SafeERC20 for IERC20; | ||
|
||
// Response data is stored here by PutResponse() and then consumed by TryCallOffchain(). | ||
// The storage slot must not be changed unless the corresponding code is updated in the Bundler. | ||
mapping(bytes32=>bytes) ResponseCache; | ||
|
||
// Owner (creator) of this contract. | ||
address public owner; | ||
|
||
// BOBA token address | ||
address public tokenAddr; | ||
|
||
// Token amount required to purchase each prepaid credit (may be 0 for testing) | ||
uint256 public pricePerCall; | ||
|
||
// Account which is used to insert system error responses. Currently a single | ||
// address but could be extended to a list of authorized accounts if needed. | ||
address public systemAccount; | ||
|
||
// Data stored per RegisteredCaller | ||
struct callerInfo { | ||
address owner; | ||
string url; | ||
uint256 credits; | ||
} | ||
|
||
// Contracts which are allowed to use Hybrid Compute. | ||
mapping(address=>callerInfo) public RegisteredCallers; | ||
|
||
// AA EntryPoint | ||
address immutable entryPoint; | ||
|
||
// Constructor | ||
constructor(address _entryPoint, address _tokenAddr) { | ||
entryPoint = _entryPoint; | ||
tokenAddr = _tokenAddr; | ||
} | ||
|
||
// Initialize system addresses. Note - can be called again to change | ||
// these addresses if necessary. | ||
function initialize(address _owner, address _systemAccount) public { | ||
require(msg.sender == owner || address(0) == owner, "Only owner"); | ||
owner = _owner; | ||
systemAccount = _systemAccount; | ||
} | ||
|
||
// Change the SystemAccount address (used for error responses) | ||
function SetSystemAccount(address _systemAccount) public { | ||
require(msg.sender == owner, "Only owner"); | ||
systemAccount = _systemAccount; | ||
} | ||
|
||
// Temporary method, until an auto-registration protocol is developed. | ||
function RegisterUrl(address contract_addr, string calldata url) public { | ||
require(msg.sender == owner, "Only owner"); | ||
RegisteredCallers[contract_addr].owner = msg.sender; | ||
RegisteredCallers[contract_addr].url = url; | ||
} | ||
|
||
// Set or change the per-call token price (0 is allowed). Does not affect | ||
// existing credit balances, only applies to new AddCredit() calls. | ||
function SetPrice(uint256 _pricePerCall) public { | ||
require(msg.sender == owner, "Only owner"); | ||
pricePerCall = _pricePerCall; | ||
} | ||
|
||
// Purchase credits allowing the specified contract to perform HC calls. | ||
// The token cost is (pricePerCall() * numCredits) and is non-refundable | ||
function AddCredit(address contract_addr, uint256 numCredits) public { | ||
if (pricePerCall > 0) { | ||
uint256 tokenPrice = numCredits * pricePerCall; | ||
IERC20(tokenAddr).safeTransferFrom(msg.sender, address(this), tokenPrice); | ||
} | ||
RegisteredCallers[contract_addr].credits += numCredits; | ||
} | ||
|
||
// Allow the owner to withdraw tokens | ||
function WithdrawTokens(uint256 amount, address withdrawTo) public { | ||
require(msg.sender == owner, "Only owner"); | ||
IERC20(tokenAddr).safeTransferFrom(address(this), withdrawTo, amount); | ||
} | ||
|
||
// Called from a HybridAccount contract, to populate the response which it will | ||
// subsequently request in TryCallOffchain() | ||
function PutResponse(bytes32 subKey, bytes calldata response) public { | ||
require(RegisteredCallers[msg.sender].owner != address(0), "Unregistered caller"); | ||
require(response.length >= 32*4, "Response too short"); | ||
|
||
(,, uint32 errCode,) = abi.decode(response,(address, uint256, uint32, bytes)); | ||
require(errCode < 2, "invalid errCode for PutResponse()"); | ||
|
||
bytes32 mapKey = keccak256(abi.encodePacked(msg.sender, subKey)); | ||
ResponseCache[mapKey] = response; | ||
} | ||
|
||
// Allow the system to supply an error response for unsuccessful requests. | ||
// Any such response will only be retrieved if there was nothing supplied | ||
// by PutResponse() | ||
function PutSysResponse(bytes32 subKey, bytes calldata response) public { | ||
require(msg.sender == systemAccount, "Only systemAccount may call PutSysResponse"); | ||
require(response.length >= 32*4, "Response too short"); | ||
|
||
(,, uint32 errCode,) = abi.decode(response,(address, uint256, uint32, bytes)); | ||
require(errCode >= 2, "PutSysResponse() may only be used for error responses"); | ||
|
||
bytes32 mapKey = keccak256(abi.encodePacked(address(this), subKey)); | ||
ResponseCache[mapKey] = response; | ||
} | ||
|
||
// Remove one or more map entries (only needed if response was not retrieved normally). | ||
function RemoveResponses(bytes32[] calldata mapKeys) public { | ||
require(msg.sender == systemAccount, "Only systemAccount may call RemoveResponses"); | ||
for (uint32 i = 0; i < mapKeys.length; i++) { | ||
delete(ResponseCache[mapKeys[i]]); | ||
} | ||
} | ||
|
||
// Try to retrieve an entry, also removing it from the mapping. This | ||
// function will check for stale entries by checking the nonce of the srcAccount. | ||
// Stale entries will return a "not found" condition. | ||
function getEntry(bytes32 mapKey) internal returns (bool, uint32, bytes memory) { | ||
bytes memory entry; | ||
bool found; | ||
uint32 errCode; | ||
bytes memory response; | ||
address srcAddr; | ||
uint256 srcNonce; | ||
|
||
entry = ResponseCache[mapKey]; | ||
if (entry.length == 1) { | ||
// Used during state simulation to verify that a trigger request actually came from this helper contract | ||
revert("_HC_VRFY"); | ||
} else if (entry.length != 0) { | ||
found = true; | ||
(srcAddr, srcNonce, errCode, response) = abi.decode(entry,(address, uint256, uint32, bytes)); | ||
uint192 nonceKey = uint192(srcNonce >> 64); | ||
|
||
INonceManager NM = INonceManager(entryPoint); | ||
uint256 actualNonce = NM.getNonce(srcAddr, nonceKey); | ||
|
||
if (srcNonce + 1 != actualNonce) { | ||
// stale entry | ||
found = false; | ||
errCode = 0; | ||
response = "0x"; | ||
} | ||
|
||
delete(ResponseCache[mapKey]); | ||
} | ||
return (found, errCode, response); | ||
} | ||
|
||
// Make an offchain call to a pre-registered endpoint. | ||
function TryCallOffchain(bytes32 userKey, bytes memory req) public returns (uint32, bytes memory) { | ||
bool found; | ||
uint32 errCode; | ||
bytes memory ret; | ||
|
||
require(RegisteredCallers[msg.sender].owner != address(0), "Calling contract not registered"); | ||
|
||
if (RegisteredCallers[msg.sender].credits == 0) { | ||
return (5, "Insufficient credit"); | ||
} | ||
RegisteredCallers[msg.sender].credits -= 1; | ||
|
||
bytes32 subKey = keccak256(abi.encodePacked(userKey, req)); | ||
bytes32 mapKey = keccak256(abi.encodePacked(msg.sender, subKey)); | ||
|
||
(found, errCode, ret) = getEntry(mapKey); | ||
|
||
if (found) { | ||
return (errCode, ret); | ||
} else { | ||
// If no off-chain response, check for a system error response. | ||
bytes32 errKey = keccak256(abi.encodePacked(address(this), subKey)); | ||
|
||
(found, errCode, ret) = getEntry(errKey); | ||
if (found) { | ||
require(errCode >= 2, "invalid errCode"); | ||
return (errCode, ret); | ||
} else { | ||
// Nothing found, so trigger a new request. | ||
bytes memory prefix = "_HC_TRIG"; | ||
bytes memory r2 = bytes.concat(prefix, abi.encodePacked(msg.sender, userKey, req)); | ||
assembly { | ||
revert(add(r2, 32), mload(r2)) | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.