Skip to content

Commit

Permalink
Move hc contracts (#12)
Browse files Browse the repository at this point in the history
* 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
mmontour1306 authored Oct 22, 2024
1 parent 4a9778d commit 9e0afd9
Show file tree
Hide file tree
Showing 22 changed files with 1,074 additions and 79 deletions.
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
branch = releases/v0.7
[submodule "crates/types/contracts/lib/account-abstraction-versions/v0_6"]
path = crates/types/contracts/lib/account-abstraction-versions/v0_6
url = https://github.com/bobanetwork/account-abstraction-hc
branch = hc-dev
url = https://github.com/eth-infinitism/account-abstraction
branch = releases/v0.6
[submodule "crates/types/contracts/lib/forge-std"]
path = crates/types/contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
Expand Down
18 changes: 9 additions & 9 deletions crates/types/contracts/hc_scripts/ExampleDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import "lib/account-abstraction-versions/v0_6/contracts/samples/HybridAccount.sol";
import "lib/account-abstraction-versions/v0_6/contracts/test/TestAuctionSystem.sol";
import "lib/account-abstraction-versions/v0_6/contracts/test/TestCaptcha.sol";
import "lib/account-abstraction-versions/v0_6/contracts/test/TestCounter.sol";
import "lib/account-abstraction-versions/v0_6/contracts/test/TestRainfallInsurance.sol";
import "lib/account-abstraction-versions/v0_6/contracts/test/TestSportsBetting.sol";
import "lib/account-abstraction-versions/v0_6/contracts/test/TestKyc.sol";
import "lib/account-abstraction-versions/v0_6/contracts/test/TestTokenPrice.sol";
import "src/hc0_6/HybridAccount.sol";
import "src/hc0_6/TestAuctionSystem.sol";
import "src/hc0_6/TestCaptcha.sol";
import "src/hc0_6/TestHybrid.sol";
import "src/hc0_6/TestRainfallInsurance.sol";
import "src/hc0_6/TestSportsBetting.sol";
import "src/hc0_6/TestKyc.sol";
import "src/hc0_6/TestTokenPrice.sol";

contract LocalDeploy is Script {
function run() external
Expand All @@ -25,7 +25,7 @@ contract LocalDeploy is Script {

ret[0] = address(new AuctionFactory(ha1Addr));
ret[1] = address(new TestCaptcha(ha1Addr));
ret[2] = address(new TestCounter(ha1Addr));
ret[2] = address(new TestHybrid(ha1Addr));
ret[3] = address(new RainfallInsurance(ha1Addr));
ret[4] = address(new SportsBetting(ha1Addr));
ret[5] = address(new TestKyc(ha1Addr));
Expand Down
4 changes: 2 additions & 2 deletions crates/types/contracts/hc_scripts/LocalDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import "lib/account-abstraction-versions/v0_6/contracts/core/EntryPoint.sol";
import "lib/account-abstraction-versions/v0_6/contracts/core/HCHelper.sol";
import "lib/account-abstraction-versions/v0_6/contracts/samples/HybridAccountFactory.sol";
import "src/hc0_6/HCHelper.sol";
import "src/hc0_6/HybridAccountFactory.sol";
import "lib/account-abstraction-versions/v0_6/contracts/samples/SimpleAccountFactory.sol";

contract LocalDeploy is Script {
Expand Down
Submodule v0_6 updated from c79c0f to fa6129
198 changes: 198 additions & 0 deletions crates/types/contracts/src/hc0_6/HCHelper.sol
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))
}
}
}
}
}
Loading

0 comments on commit 9e0afd9

Please sign in to comment.