Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update for new axiom-std and SDK #1

Merged
merged 26 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
name: tests

on: workflow_dispatch
on:
workflow_dispatch:
pull_request:
push:
branches:
- "**"

env:
FOUNDRY_PROFILE: ci
Expand Down Expand Up @@ -30,5 +35,6 @@ jobs:

- name: Run Forge tests
run: |
forge test -vvv
export PROVIDER_URI_SEPOLIA=${{ secrets.PROVIDER_URI_SEPOLIA }}
forge test -vvvv
id: test
9 changes: 6 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/axiom-v2-periphery"]
path = lib/axiom-v2-periphery
url = https://github.com/axiom-crypto/axiom-v2-periphery
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/axiom-std"]
path = lib/axiom-std
url = https://github.com/axiom-crypto/axiom-std
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Asset Refund (using Axiom Client SDK)
# Unsupported Asset Refund

This example allows users to claim a refund of UNI token sent to the specified address (currently [0xe534b1d79cB4C8e11bEB93f00184a12bd85a63fD](https://sepolia.etherscan.io/address/0xe534b1d79cB4C8e11bEB93f00184a12bd85a63fD)) on the Sepolia testnet. Users utilize a data-fetching layer on top of Axiom to prove that their account matches some parameters before submitting a Query. In this case, the parameters are that the user has sent UNI to the specified address. The entire amount in UNI is refunded, and a refund can be claimed exactly once for each transaction. Currently, only the most recent transaction can be refunded.
Unsupported Asset Refund (UAR) allows the owner of any address on Ethereum (EOA or smart contract) to offer self-serve refunds for a set of ERC-20 tokens sent to that address which are designated as **unsupported**. To opt in to the UAR system for a specific ERC-20, contract owners simply give an allowance to UAR, which they can revoke at any time.

This example was created by writing a client circuit with the [Axiom Client SDK](https://github.com/axiom-crypto/axiom-sdk-client) and using it to generate Axiom queries inside a webapp using [Axiom SDK React Components](https://www.npmjs.com/package/@axiom-crypto/react).
Users who have accidentally sent unsupported ERC-20 tokens to an address can claim a refund by proving their previous transfers to UAR with Axiom. UAR will validate these transfers and issue a refund to users, all without any intervention from the original contract owner. UAR ensures that a refund can be claimed exactly once for each transaction.

## dApp
## Development

[`/app`](./app) is a full Next.js 14 implementation of the Asset Refund dApp. You will need to fill in an `.env.local` file in that folder for the Next.js app to run.
To set up the development environment, run:

## Axiom Circuit

The Axiom client circuit code and supporting files are is located in [`./app/axiom`](./app/axiom).
```bash
forge install
npm install # or `yarn install` or `pnpm install`
```
45 changes: 0 additions & 45 deletions app/axiom/refundEvent.circuit.ts

This file was deleted.

111 changes: 111 additions & 0 deletions app/axiom/unsupportedAssetRefund.circuit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
isEqual,
isLessThan,
checkLessThan,
addToCallback,
CircuitValue,
CircuitValue256,
constant,
witness,
checkEqual,
mul,
mulAdd,
selectFromIdx,
sub,
add,
or,
isZero,
getReceipt,
getTx,
} from "@axiom-crypto/client";

/// For type safety, define the input types to your circuit here.
/// These should be the _variable_ inputs to your circuit. Constants can be hard-coded into the circuit itself.
export interface CircuitInputs {
numClaims: CircuitValue;
blockNumbers: CircuitValue[];
txIdxs: CircuitValue[];
logIdxs: CircuitValue[];
}

/// Corresponds to: https://sepolia.etherscan.io/tx/0xc61887a5aa0d38c7a70b23c1d68d12f74255159fa2bae588fa67a016f96052bb
export const defaultInputs = {
numClaims: 1,
blockNumbers: [5141305, 5141305, 5141305, 5141305, 5141305, 5141305, 5141305, 5141305, 5141305, 5141305],
txIdxs: [44, 44, 44, 44, 44, 44, 44, 44, 44, 44],
logIdxs: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
}

// The function name `circuit` is searched for by default by our Axiom CLI; if you decide to
// change the function name, you'll also need to ensure that you also pass the Axiom CLI flag
// `-f <circuitFunctionName>` for it to work
export const circuit = async (inputs: CircuitInputs) => {
// Define the event schema for ERC-20 transfer
// Transfer(address indexed from, address indexed to, uint256 value)
const eventSchema =
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";

const MAX_NUM_CLAIMS = 10;

// Check that the number of claims is valid
if (inputs.blockNumbers.length != MAX_NUM_CLAIMS || inputs.txIdxs.length != MAX_NUM_CLAIMS || inputs.logIdxs.length != MAX_NUM_CLAIMS) {
throw new Error("blockNumbers or txIdxs or logIdxs does not match MAX_NUM_CLAIMS");
}
if (inputs.numClaims.value() > MAX_NUM_CLAIMS) {
throw new Error("Number of claims exceeds maximum");
}
checkLessThan(inputs.numClaims, constant(MAX_NUM_CLAIMS + 1));
checkLessThan(constant(0), inputs.numClaims);

let claimIds: CircuitValue[] = [];
let inRanges: CircuitValue[] = [];
for (var idx = 0; idx < MAX_NUM_CLAIMS; idx++) {
const inRange = isLessThan(constant(idx), inputs.numClaims);
const id_1 = mulAdd(inputs.blockNumbers[idx], BigInt(2 ** 64), inputs.txIdxs[idx]);
const id = mulAdd(id_1, BigInt(2 ** 64), inputs.logIdxs[idx]);

inRanges.push(inRange);
claimIds.push(mul(id, inRange));
}

for (let idx = 1; idx < MAX_NUM_CLAIMS; idx++) {
const isLess = isLessThan(claimIds[idx - 1], claimIds[idx]);
const isLessIfValid = or(isLess, isZero(inRanges[idx]));
checkEqual(isLessIfValid, 1);
}

const lastClaimId = selectFromIdx(claimIds, sub(inputs.numClaims, constant(1)));;

let totalValue: CircuitValue = constant(0);
let tokenContractAddress: CircuitValue[] = [];
let fromAddress: CircuitValue[] = [];
let toAddress: CircuitValue[] = [];
for (var idx = 0; idx < MAX_NUM_CLAIMS; idx++) {
const isValid = inRanges[idx];

const receipt = getReceipt(inputs.blockNumbers[idx], inputs.txIdxs[idx]);
const receiptLog = receipt.log(inputs.logIdxs[idx]);

// check that all receipts are emitted by the same address and to / from the same address
const receiptAddress = await receiptLog.address();
const transferFrom = await receiptLog.topic(1, eventSchema);
const transferTo = await receiptLog.topic(2, eventSchema);
const transferValue: CircuitValue = (await receiptLog.data(0, eventSchema)).toCircuitValue();

tokenContractAddress[idx] = receiptAddress.toCircuitValue();
fromAddress[idx] = transferFrom.toCircuitValue();
toAddress[idx] = transferTo.toCircuitValue();
checkEqual(constant(1), or(isZero(isValid), isEqual(tokenContractAddress[0], receiptAddress.toCircuitValue())));
checkEqual(constant(1), or(isZero(isValid), isEqual(fromAddress[0], transferFrom.toCircuitValue())));
checkEqual(constant(1), or(isZero(isValid), isEqual(toAddress[0], transferTo.toCircuitValue())));

totalValue = add(totalValue, mul(isValid, transferValue));
}

addToCallback(fromAddress[0]);
addToCallback(toAddress[0]);
addToCallback(totalValue);
addToCallback(tokenContractAddress[0]);
addToCallback(claimIds[0]);
addToCallback(lastClaimId);
};
58 changes: 29 additions & 29 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
},
"main": "index.js",
"dependencies": {
"@axiom-crypto/client": "2.0.3",
"@axiom-crypto/react": "2.0.3",
"@axiom-crypto/client": "2.0.4",
"@axiom-crypto/react": "2.0.4",
"@tanstack/react-query": "^5.17.12",
"@types/node": "20.6.2",
"@types/react": "18.2.48",
Expand Down
5 changes: 2 additions & 3 deletions app/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ export default async function Home() {
return (
<>
<Title>
Asset Refund Example
Unsupported Asset Refund
</Title>
<div className="text-center">
Anyone who has sent UNI token to the specified address on Sepolia testnet is eligible for a
refund of their UNI.
Under construction: Do not use
</div>
<AdvanceStepButton
label="Generate Proof"
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/layout/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function Navbar() {
<div className="flex flex-row justify-between items-center w-full px-8 py-4 border-b-[1px] border-darkgrey shadow-md">
<Link href="/" className="text-xl text-white font-mono">
<div >
Asset Refund Example
Unsupported Asset Refund
</div>
</Link>
<div className="flex flex-row items-center gap-4 sm:gap-8">
Expand All @@ -22,7 +22,7 @@ export default function Navbar() {
<Link href="https://docs.axiom.xyz/">
Docs
</Link>
<Link href="https://github.com/bfan05/asset-refund-example">
<Link href="https://github.com/axiom-crypto/unsupported-asset-refund">
Github
</Link>
<ConnectWallet />
Expand Down
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ solc = "0.8.19"
libs = ["node_modules", "lib"]
remappings = [
"@axiom-crypto/v2-periphery/=lib/axiom-v2-periphery/src/",
"@axiom-crypto/axiom-std/=lib/axiom-std/src/",
"@openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
"ds-test/=lib/forge-std/lib/ds-test/src/",
"forge-std/=lib/forge-std/src/"
Expand Down
1 change: 1 addition & 0 deletions lib/axiom-std
Submodule axiom-std added at 089c09
1 change: 1 addition & 0 deletions lib/axiom-v2-periphery
Submodule axiom-v2-periphery added at 37a9eb
1 change: 1 addition & 0 deletions lib/forge-std
Submodule forge-std added at ae570f
Loading