Skip to content

Commit

Permalink
Support the new word-guessing game. Dynamic gas calculation.
Browse files Browse the repository at this point in the history
Misc. refactoring.
 Changes to be committed:
	modified:   crates/rpc/src/eth/api.rs
	modified:   crates/types/contracts/lib/account-abstraction
	modified:   crates/types/src/hybrid_compute.rs
	modified:   hybrid-compute/Dockerfile.offchain-rpc
	modified:   hybrid-compute/deploy.py
	modified:   hybrid-compute/offchain.py
	modified:   hybrid-compute/userop.py
  • Loading branch information
mmontour1306 committed May 23, 2024
1 parent c7927ea commit 7d0cea3
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 36 deletions.
3 changes: 2 additions & 1 deletion crates/rpc/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,9 @@ where
println!("HC api.rs offchain_gas estimate {:?} sum {:?}", r2, offchain_gas);
println!("HC api.rs userop_gas estimate {:?} sum {:?}", r3, op_gas);
println!("HC api.rs cleanup_gas estimate {:?} sum {:?}", r4, cleanup_gas);

let needed_pvg = r3.pre_verification_gas + offchain_gas;
hybrid_compute::hc_set_pvg(hh, needed_pvg, offchain_gas + cleanup_gas);
hybrid_compute::hc_set_pvg(hh, needed_pvg, offchain_gas + cleanup_gas + offchain_gas);

return Ok(GasEstimate {
pre_verification_gas: needed_pvg,
Expand Down
2 changes: 1 addition & 1 deletion crates/types/contracts/lib/account-abstraction
6 changes: 4 additions & 2 deletions crates/types/src/hybrid_compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,16 @@ pub async fn external_op(
let merged_response = AbiEncode::encode((src_addr, nonce, err_code, tmp_bytes));

let call_data = make_op_calldata(cfg.helper_addr, sub_key, Bytes::from(merged_response.to_vec()));
println!("HC external_op call_data {:?}", call_data);
let call_gas = 705*response_payload.len() + 170000;

println!("HC external_op call_data len {:?} {:?} gas {:?} {:?}", response_payload.len(), call_data.len(), call_gas, call_data);

let mut new_op:UserOperation = UserOperation{
sender: ep_addr,
nonce: oo_nonce.into(),
init_code: Bytes::new(),
call_data: call_data.clone(),
call_gas_limit: U256::from(0x30000),
call_gas_limit: U256::from(call_gas),
verification_gas_limit: U256::from(0x10000),
pre_verification_gas: U256::from(0x10000),
max_fee_per_gas: U256::zero(),
Expand Down
2 changes: 2 additions & 0 deletions hybrid-compute/Dockerfile.offchain-rpc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
FROM python:3.8-slim
RUN apt update
RUN apt install wamerican # for /usr/share/dict/words
RUN pip3 install web3 jsonrpclib
COPY ./offchain.py /
CMD [ "python", "-u", "./offchain.py" ]
2 changes: 1 addition & 1 deletion hybrid-compute/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def deploy2(name, cc, salt):
saAddr = deploy2("SimpleAccount", SA.constructor(epAddr),0)
ha0Addr = deploy2("HybridAccount.0", HA.constructor(epAddr, hhAddr),0)
ha1Addr = deploy2("HybridAccount.1", HA.constructor(epAddr, hhAddr),1)
tcAddr = deploy2("TestCounter", TC.constructor(hhAddr),0)
tcAddr = deploy2("TestCounter", TC.constructor(ha1Addr),0)

with open("./contracts.json", "w") as f:
f.write(json.dumps(deployed))
Expand Down
58 changes: 54 additions & 4 deletions hybrid-compute/offchain.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import os,sys
import os,sys,re,random
from web3 import Web3, exceptions
from eth_abi import abi as ethabi
import eth_account
Expand Down Expand Up @@ -33,6 +33,18 @@

# -------------------------------------------------------------

# Load a list of 4-letter dictionary words for a text generation example.
def load_words():
wordlist = []
with open("/usr/share/dict/words", "r") as f:
p = re.compile('^[a-z]{4}$')
for line in f.readlines():
line = line.strip()
if p.match(line) and line != "frog": # Reserved for "cheat" mode
wordlist.append(line)
return wordlist
wordlist = load_words()

def selector(name):
nameHash = Web3.to_hex(Web3.keccak(text=name))
return nameHash[2:10]
Expand All @@ -43,13 +55,15 @@ def gen_response(req, err_code, resp_payload):
p_enc1 = "0x" + selector("PutResponse(bytes32,bytes)") + Web3.to_hex(enc1)[2:] # dfc98ae8

enc2 = ethabi.encode(['address', 'uint256', 'bytes'], [Web3.to_checksum_address(HelperAddr), 0, Web3.to_bytes(hexstr=p_enc1)])
p_enc2 = selector("execute(address,uint256,bytes)") + Web3.to_hex(enc2)[2:] # b61d27f6
p_enc2 = "0x" + selector("execute(address,uint256,bytes)") + Web3.to_hex(enc2)[2:] # b61d27f6

limits = {
'callGasLimit': "0x30000",
'verificationGasLimit': "0x10000",
'preVerificationGas': "0x10000",
}
callGas = 705*len(resp_payload) + 170000

print("callGas calculation", len(resp_payload),4+len(enc2), callGas)
p = ethabi.encode([
'address',
'uint256',
Expand All @@ -66,7 +80,7 @@ def gen_response(req, err_code, resp_payload):
req['opNonce'],
Web3.keccak(Web3.to_bytes(hexstr='0x')), # initCode
Web3.keccak(Web3.to_bytes(hexstr=p_enc2)),
Web3.to_int(hexstr=limits['callGasLimit']),
callGas,
Web3.to_int(hexstr=limits['verificationGasLimit']),
Web3.to_int(hexstr=limits['preVerificationGas']),
0, # maxFeePerGas
Expand Down Expand Up @@ -122,6 +136,40 @@ def offchain_addsub2(sk, src_addr, src_nonce, oo_nonce, payload, *args):

return gen_response(req, err_code, resp)

# Demo method, returns a string containing a given number of random words
def offchain_ramble(sk, src_addr, src_nonce, oo_nonce, payload, *args):
global wordlist
print(" -> offchain_ramble handler called with subkey={} src_addr={} src_nonce={} oo_nonce={} payload={} extra_args={}".format(sk, src_addr, src_nonce, oo_nonce, payload, args))
err_code = 1
resp = Web3.to_bytes(text="unknown error")

try:
req = parse_req(sk, src_addr, src_nonce, oo_nonce, payload)
dec = ethabi.decode(['uint256','bool'], req['reqBytes'])
n = dec[0]
cheat = dec[1]
words = []

if n >= 1 and n < 1000:
for i in range(n):
r = random.randint(0,len(wordlist)-1)
words.append(wordlist[r])

if cheat:
pos = random.randint(0,len(words)-1)
print("Cheat at position", pos)
words[pos] = "frog"

resp = ethabi.encode(['string[]'], [words])
err_code = 0
else:
print("Invalid length", n)
resp = Web3.to_bytes(text="invalid string length")
except Exception as e:
print("DECODE FAILED", e)

return gen_response(req, err_code, resp)

# -------------------------------------------------------------

class RequestHandler(SimpleJSONRPCRequestHandler):
Expand All @@ -130,6 +178,8 @@ class RequestHandler(SimpleJSONRPCRequestHandler):
def server_loop():
server = SimpleJSONRPCServer(('0.0.0.0', PORT), requestHandler=RequestHandler)
server.register_function(offchain_addsub2, selector("addsub2(uint32,uint32)")) # 97e0d7ba
server.register_function(offchain_ramble, selector("ramble(uint256,bool)"))
server.serve_forever()


server_loop() # Run until killed
119 changes: 92 additions & 27 deletions hybrid-compute/userop.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
bundler_addr = Web3.to_checksum_address("0xB834a876b7234eb5A45C0D5e693566e8842400bB")
bundler_key = "0xf91be07ef5a01328015cae4f2e5aefe3c4577a90abb8e2e913fe071b0e3732ed"

bundler_rpc = "http://127.0.0.1:3300"

# -------------------------------------------------------------

w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:9545"))
Expand Down Expand Up @@ -65,12 +67,16 @@ def showBalances():
print("SA ", EP.functions.getDepositInfo(SA.address).call(), w3.eth.get_balance(SA.address))
print("BA ", EP.functions.getDepositInfo(BA.address).call(), w3.eth.get_balance(BA.address))
print("HA ", EP.functions.getDepositInfo(HA.address).call(), w3.eth.get_balance(HA.address))
print("TC ", EP.functions.getDepositInfo(TC.address).call(), w3.eth.get_balance(TC.address))

showBalances()
balStart_bnd = w3.eth.get_balance(bundler_addr)
balStart_sa = EP.functions.getDepositInfo(SA.address).call()[0]

# -------------------------------------------------------------
def selector(name):
nameHash = Web3.to_hex(Web3.keccak(text=name))
return nameHash[2:10]

def signAndSubmit(tx, key):
signed_txn =w3.eth.account.sign_transaction(tx, key)
Expand Down Expand Up @@ -276,16 +282,44 @@ def ParseReceipt(opReceipt):
l1Fees += Web3.to_int(hexstr=txRcpt['l1Fee'])
#exit(0)

def submitOp(p):
opHash = EP.functions.getUserOpHash(packOp(p)).call()
eMsg = eth_account.messages.encode_defunct(opHash)
sig = w3.eth.account.sign_message(eMsg, private_key=u_key)
p['signature'] = Web3.to_hex(sig.signature)

response = requests.post(bundler_rpc, json=request("eth_sendUserOperation", params=[p, EP.address]))
print("sendOperation response", response.json())

opHash = {}
opHash['hash'] = response.json()['result']
timeout = True
for i in range(10):
print("Waiting for receipt...")
time.sleep(1)
opReceipt = requests.post(bundler_rpc, json=request("eth_getUserOperationReceipt", params=opHash))
opReceipt = opReceipt.json()['result']
if opReceipt is not None:
#print("opReceipt", opReceipt)
assert(opReceipt['receipt']['status'] == "0x1")
print("operation success", opReceipt['success'])
ParseReceipt(opReceipt)
timeout = False
assert(opReceipt['success'])
break
if timeout:
print("*** Previous operation timed out")
exit(1)

def TestAddSub2 (a, b):
global estGas
print("\n - - - - TestAddSub2({},{}) - - - -".format(a,b))
print("TestCount(begin)=", TC.functions.counters(SA.address).call())
# 0xb83adc8b = count(uint256,address)
# 0x2e41763e = count(uint32,uint32,address)
countCall = Web3.to_bytes(hexstr="0x2e41763e") + ethabi.encode(['uint32', 'uint32', 'address'], [a, b, HA.address]) #HERE

# 0xb61d27f6 = execute(address,uint256,bytes)
exCall = Web3.to_bytes(hexstr="0xb61d27f6") + ethabi.encode(['address','uint256','bytes'],[TC.address,0,countCall])
countCall = Web3.to_bytes(hexstr="0x"+selector("count(uint32,uint32)")) + ethabi.encode(['uint32', 'uint32'], [a, b])

exCall = Web3.to_bytes(hexstr="0x"+selector("execute(address,uint256,bytes)")) + \
ethabi.encode(['address','uint256','bytes'],[TC.address, 0, countCall])

p = buildOp(SA, nKey, exCall)

Expand All @@ -295,7 +329,7 @@ def TestAddSub2 (a, b):
p['signature'] = Web3.to_hex(sig.signature)

j = [p, EP.address]
response = requests.post("http://localhost:3300/", json=request("eth_estimateUserOperationGas", params=j))
response = requests.post(bundler_rpc, json=request("eth_estimateUserOperationGas", params=j))
print("estimateGas response", response.json())

if 'error' in response.json():
Expand All @@ -315,41 +349,71 @@ def TestAddSub2 (a, b):
p['callGasLimit'] = Web3.to_hex(Web3.to_int(hexstr=est_result['callGasLimit']) + 0)
estGas = Web3.to_int(hexstr=est_result['preVerificationGas']) + Web3.to_int(hexstr=est_result['verificationGasLimit']) + Web3.to_int(hexstr=est_result['callGasLimit'])
print("estimateGas total =", estGas)

print("-----")
time.sleep(5)
submitOp(p)
print("TestCount(end)=", TC.functions.counters(SA.address).call())

def TestWordGuess (n, cheat):
global estGas
print("\n - - - - TestWordGuess({},{}) - - - -".format(n, cheat))
gameCall = Web3.to_bytes(hexstr="0x"+selector("wordGuess(string,bool)")) + \
ethabi.encode(['string', 'bool'], ["frog", cheat])

perEntry = TC.functions.EntryCost().call();
print("Pool balance before playing =", Web3.from_wei(TC.functions.Pool().call(),'gwei'))

exCall = Web3.to_bytes(hexstr="0x"+selector("execute(address,uint256,bytes)")) + \
ethabi.encode(['address','uint256','bytes'],[TC.address, n * perEntry, gameCall])
p = buildOp(SA, nKey, exCall)

opHash = EP.functions.getUserOpHash(packOp(p)).call()
eMsg = eth_account.messages.encode_defunct(opHash)
sig = w3.eth.account.sign_message(eMsg, private_key=u_key)
p['signature'] = Web3.to_hex(sig.signature)

j = [p, EP.address]
response = requests.post(bundler_rpc, json=request("eth_estimateUserOperationGas", params=j))
print("estimateGas response", response.json())

if 'error' in response.json():
print("*** eth_estimateUserOperationGas failed")
time.sleep(2)
if True:
return
print("*** Continuing after failure")
p['preVerificationGas'] = "0xffff"
p['verificationGasLimit'] = "0xffff"
p['callGasLimit'] = "0x40000"
else:
est_result = response.json()['result']

p['preVerificationGas'] = Web3.to_hex(Web3.to_int(hexstr=est_result['preVerificationGas']) + 0)
p['verificationGasLimit'] = Web3.to_hex(Web3.to_int(hexstr=est_result['verificationGasLimit']) + 0)
p['callGasLimit'] = Web3.to_hex(Web3.to_int(hexstr=est_result['callGasLimit']) + 0)
estGas = Web3.to_int(hexstr=est_result['preVerificationGas']) + Web3.to_int(hexstr=est_result['verificationGasLimit']) + Web3.to_int(hexstr=est_result['callGasLimit'])
print("estimateGas total =", estGas)

print("-----")
time.sleep(5)
response = requests.post("http://localhost:3300/", json=request("eth_sendUserOperation", params=[p, EP.address]))
print("sendOperation response", response.json())
submitOp(p)
print("Pool balance after playing =", Web3.from_wei(TC.functions.Pool().call(),'gwei'))

# ===============================================


opHash = {}
opHash['hash'] = response.json()['result']
timeout = True
for i in range(10):
print("Waiting for receipt...")
time.sleep(1)
opReceipt = requests.post("http://localhost:3300/", json=request("eth_getUserOperationReceipt", params=opHash))
opReceipt = opReceipt.json()['result']
if opReceipt is not None:
#print("opReceipt", opReceipt)
assert(opReceipt['receipt']['status'] == "0x1")
print("operation success", opReceipt['success'])
ParseReceipt(opReceipt)
timeout = False
break
print("TestCount(end)=", TC.functions.counters(SA.address).call())
if timeout:
print("*** Previous operation timed out")
exit(1)
TestAddSub2(2, 1) # Success
TestAddSub2(2, 10) # Underflow error, asserted
TestAddSub2(2, 3) # Underflow error, handled internally
TestAddSub2(7, 0) # Not HC
TestAddSub2(4, 1) # Success again

TestWordGuess(1, False)
TestWordGuess(10, False)
TestWordGuess(100, False)
TestWordGuess(2, True)

showBalances()
balFinal_bnd = w3.eth.get_balance(bundler_addr)
balFinal_sa = EP.functions.getDepositInfo(SA.address).call()[0]
Expand All @@ -359,6 +423,7 @@ def TestAddSub2 (a, b):
userPaid = balStart_sa - balFinal_sa
bundlerProfit = balFinal_bnd - balStart_bnd
print("User account paid:", userPaid)
assert(userPaid > 0)
print(" Bundler profit:", bundlerProfit, 100*(bundlerProfit / userPaid), "%")
print(" L2 gas:", l2Fees, 100*(l2Fees / userPaid), "%")
print(" L1 fee:", l1Fees, 100*(l1Fees / userPaid), "%")
Expand Down

0 comments on commit 7d0cea3

Please sign in to comment.