Skip to content

Commit 119281e

Browse files
authored
Merge pull request #2172 from opentensor/feat/evm-pure-proxy-precompile
Feat/evm pure proxy precompile
2 parents 5557500 + bfb1111 commit 119281e

File tree

11 files changed

+689
-7
lines changed

11 files changed

+689
-7
lines changed

common/src/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,33 @@ pub enum ProxyType {
162162
RootClaim,
163163
}
164164

165+
impl TryFrom<u8> for ProxyType {
166+
type Error = ();
167+
168+
fn try_from(value: u8) -> Result<Self, Self::Error> {
169+
match value {
170+
0 => Ok(Self::Any),
171+
1 => Ok(Self::Owner),
172+
2 => Ok(Self::NonCritical),
173+
3 => Ok(Self::NonTransfer),
174+
4 => Ok(Self::Senate),
175+
5 => Ok(Self::NonFungible),
176+
6 => Ok(Self::Triumvirate),
177+
7 => Ok(Self::Governance),
178+
8 => Ok(Self::Staking),
179+
9 => Ok(Self::Registration),
180+
10 => Ok(Self::Transfer),
181+
11 => Ok(Self::SmallTransfer),
182+
12 => Ok(Self::RootWeights),
183+
13 => Ok(Self::ChildKeys),
184+
14 => Ok(Self::SudoUncheckedSetCode),
185+
15 => Ok(Self::SwapHotkey),
186+
16 => Ok(Self::SubnetLeaseBeneficiary),
187+
_ => Err(()),
188+
}
189+
}
190+
}
191+
165192
impl Default for ProxyType {
166193
// allow all Calls; required to be most permissive
167194
fn default() -> Self {

evm-tests/src/address-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Address } from "viem"
22
import { encodeAddress } from "@polkadot/util-crypto";
3-
import { ss58Address } from "@polkadot-labs/hdkd-helpers";
3+
import { ss58Address, ss58Decode } from "@polkadot-labs/hdkd-helpers";
44
import { hexToU8a } from "@polkadot/util";
55
import { blake2AsU8a, decodeAddress } from "@polkadot/util-crypto";
66
import { Binary } from "polkadot-api";

evm-tests/src/contracts/proxy.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
export const IPROXY_ADDRESS = "0x000000000000000000000000000000000000080b";
2+
3+
export const IProxyABI = [
4+
{
5+
"inputs": [
6+
{
7+
"internalType": "uint8",
8+
"name": "proxy_type",
9+
"type": "uint8"
10+
},
11+
{
12+
"internalType": "uint32",
13+
"name": "delay",
14+
"type": "uint32"
15+
},
16+
{
17+
"internalType": "uint16",
18+
"name": "index",
19+
"type": "uint16"
20+
}
21+
],
22+
"name": "createPureProxy",
23+
"outputs": [
24+
{
25+
"internalType": "bytes32",
26+
"name": "proxy",
27+
"type": "bytes32"
28+
}
29+
],
30+
"stateMutability": "nonpayable",
31+
"type": "function"
32+
},
33+
{
34+
"inputs": [
35+
{
36+
"internalType": "bytes32",
37+
"name": "spawner",
38+
"type": "bytes32"
39+
},
40+
{
41+
"internalType": "uint8",
42+
"name": "proxy_type",
43+
"type": "uint8"
44+
},
45+
{
46+
"internalType": "uint16",
47+
"name": "index",
48+
"type": "uint16"
49+
},
50+
{
51+
"internalType": "uint32",
52+
"name": "height",
53+
"type": "uint32"
54+
},
55+
{
56+
"internalType": "uint32",
57+
"name": "ext_index",
58+
"type": "uint32"
59+
}
60+
],
61+
"name": "killPureProxy",
62+
"outputs": [],
63+
"stateMutability": "nonpayable",
64+
"type": "function"
65+
},
66+
{
67+
"inputs": [
68+
{
69+
"internalType": "bytes32",
70+
"name": "real",
71+
"type": "bytes32"
72+
},
73+
{
74+
"internalType": "uint8[]",
75+
"name": "force_proxy_type", // optional
76+
"type": "uint8[]"
77+
},
78+
{
79+
"internalType": "uint8[]",
80+
"name": "call",
81+
"type": "uint8[]"
82+
}
83+
],
84+
"name": "proxyCall",
85+
"outputs": [],
86+
"stateMutability": "nonpayable",
87+
"type": "function"
88+
},
89+
{
90+
"inputs": [],
91+
"name": "removeProxies",
92+
"outputs": [],
93+
"stateMutability": "nonpayable",
94+
"type": "function"
95+
}, {
96+
"inputs": [],
97+
"name": "pokeDeposit",
98+
"outputs": [],
99+
"stateMutability": "nonpayable",
100+
"type": "function"
101+
},
102+
{
103+
"inputs": [
104+
{
105+
"internalType": "bytes32",
106+
"name": "delegate",
107+
"type": "bytes32"
108+
},
109+
{
110+
"internalType": "uint8",
111+
"name": "proxy_type",
112+
"type": "uint8"
113+
},
114+
{
115+
"internalType": "uint32",
116+
"name": "delay",
117+
"type": "uint32"
118+
}
119+
],
120+
"name": "removeProxy",
121+
"outputs": [],
122+
"stateMutability": "nonpayable",
123+
"type": "function"
124+
},
125+
{
126+
"inputs": [
127+
{
128+
"internalType": "bytes32",
129+
"name": "delegate",
130+
"type": "bytes32"
131+
},
132+
{
133+
"internalType": "uint8",
134+
"name": "proxy_type",
135+
"type": "uint8"
136+
},
137+
{
138+
"internalType": "uint32",
139+
"name": "delay",
140+
"type": "uint32"
141+
}
142+
],
143+
"name": "addProxy",
144+
"outputs": [],
145+
"stateMutability": "nonpayable",
146+
"type": "function"
147+
}
148+
];
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import * as assert from "assert";
2+
3+
import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate"
4+
import { generateRandomEthersWallet } from "../src/utils";
5+
import { devnet, MultiAddress } from "@polkadot-api/descriptors"
6+
import { PolkadotSigner, TypedApi } from "polkadot-api";
7+
import { convertH160ToPublicKey, convertH160ToSS58, convertPublicKeyToSs58 } from "../src/address-utils"
8+
import { IProxyABI, IPROXY_ADDRESS } from "../src/contracts/proxy"
9+
import { ethers } from 'ethers';
10+
import { forceSetBalanceToEthAddress, forceSetBalanceToSs58Address } from "../src/subtensor";
11+
import { KeyPair } from "@polkadot-labs/hdkd-helpers";
12+
13+
import { decodeAddress } from "@polkadot/util-crypto";
14+
15+
async function getTransferCallCode(api: TypedApi<typeof devnet>, receiver: KeyPair, transferAmount: number) {
16+
17+
const unsignedTx = api.tx.Balances.transfer_keep_alive({
18+
dest: MultiAddress.Id(convertPublicKeyToSs58(receiver.publicKey)),
19+
value: BigInt(1000000000),
20+
});
21+
const encodedCallDataBytes = await unsignedTx.getEncodedData();
22+
23+
// encoded call should be 0x050300d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d02286bee
24+
// const transferCall = encodedCallDataBytes
25+
26+
const data = encodedCallDataBytes.asBytes()
27+
28+
return [...data]
29+
}
30+
31+
async function getProxies(api: TypedApi<typeof devnet>, address: string) {
32+
const entries = await api.query.Proxy.Proxies.getEntries()
33+
const result = []
34+
for (const entry of entries) {
35+
const proxyAddress = entry.keyArgs[0]
36+
const values = entry.value
37+
const proxies = values[0]
38+
for (const proxy of proxies) {
39+
if (proxy.delegate === address) {
40+
result.push(proxyAddress)
41+
}
42+
}
43+
}
44+
return result
45+
}
46+
47+
describe("Test pure proxy precompile", () => {
48+
const evmWallet = generateRandomEthersWallet();
49+
const evmWallet2 = generateRandomEthersWallet();
50+
const evmWallet3 = generateRandomEthersWallet();
51+
const receiver = getRandomSubstrateKeypair();
52+
53+
let api: TypedApi<typeof devnet>
54+
55+
let alice: PolkadotSigner;
56+
57+
before(async () => {
58+
api = await getDevnetApi()
59+
alice = await getAliceSigner();
60+
61+
await forceSetBalanceToEthAddress(api, evmWallet.address)
62+
await forceSetBalanceToEthAddress(api, evmWallet2.address)
63+
await forceSetBalanceToEthAddress(api, evmWallet3.address)
64+
})
65+
66+
it("Call createPureProxy, then use proxy to call transfer", async () => {
67+
const proxies = await getProxies(api, convertH160ToSS58(evmWallet.address))
68+
const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet)
69+
console.log("evmWallet", evmWallet.address)
70+
71+
const type = 0;
72+
const delay = 0;
73+
const index = 0;
74+
const tx = await contract.createPureProxy(type, delay, index)
75+
const response = await tx.wait()
76+
console.log("response", response.blockNumber)
77+
78+
const proxiesAfterAdd = await getProxies(api, convertH160ToSS58(evmWallet.address))
79+
80+
const length = proxiesAfterAdd.length
81+
assert.equal(length, proxies.length + 1, "proxy should be set")
82+
const proxy = proxiesAfterAdd[proxiesAfterAdd.length - 1]
83+
84+
await forceSetBalanceToSs58Address(api, proxy)
85+
const balance = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free
86+
87+
const amount = 1000000000;
88+
89+
const callCode = await getTransferCallCode(api, receiver, amount)
90+
const tx2 = await contract.proxyCall(decodeAddress(proxy), [type], callCode)
91+
await tx2.wait()
92+
93+
const balanceAfter = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free
94+
assert.equal(balanceAfter, balance + BigInt(amount), "balance should be increased")
95+
})
96+
97+
it("Call createPureProxy, add multiple proxies", async () => {
98+
const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet)
99+
const type = 0;
100+
const delay = 0;
101+
const index = 0;
102+
const proxies = await getProxies(api, convertH160ToSS58(evmWallet.address))
103+
const length = proxies.length
104+
for (let i = 0; i < 5; i++) {
105+
const tx = await contract.createPureProxy(type, delay, index)
106+
await tx.wait()
107+
108+
await new Promise(resolve => setTimeout(resolve, 500));
109+
const currentProxies = await getProxies(api, convertH160ToSS58(evmWallet.address))
110+
assert.equal(currentProxies.length, length + i + 1, "proxy should be set")
111+
}
112+
})
113+
114+
it("Call createPureProxy, edge cases, call via wrong proxy", async () => {
115+
const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet2)
116+
const amount = 1000000000;
117+
const callCode = await getTransferCallCode(api, receiver, amount)
118+
const type = 0;
119+
120+
// call with wrong proxy
121+
try {
122+
const tx = await contract.proxyCall(receiver, [type], callCode)
123+
await tx.wait()
124+
} catch (error) {
125+
assert.notEqual(error, undefined, "should fail if proxy not set")
126+
}
127+
})
128+
129+
it("Call createProxy, then use proxy to call transfer", async () => {
130+
const proxies = await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet2.address))
131+
const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet2)
132+
133+
const type = 0;
134+
const delay = 0;
135+
136+
const tx = await contract.addProxy(convertH160ToPublicKey(evmWallet3.address), type, delay)
137+
await tx.wait()
138+
139+
140+
const proxiesAfterAdd = await await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet2.address))
141+
142+
const length = proxiesAfterAdd[0].length
143+
assert.equal(length, proxies[0].length + 1, "proxy should be set")
144+
const proxy = proxiesAfterAdd[0][proxiesAfterAdd[0].length - 1]
145+
146+
assert.equal(proxy.delegate, convertH160ToSS58(evmWallet3.address), "proxy should be set")
147+
148+
149+
const balance = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free
150+
151+
const amount = 1000000000;
152+
153+
const contract2 = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet3)
154+
155+
156+
const callCode = await getTransferCallCode(api, receiver, amount)
157+
const tx2 = await contract2.proxyCall(convertH160ToPublicKey(evmWallet2.address), [type], callCode)
158+
await tx2.wait()
159+
160+
const balanceAfter = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free
161+
assert.equal(balanceAfter, balance + BigInt(amount), "balance should be increased")
162+
})
163+
});

pallets/admin-utils/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ pub mod pallet {
139139
Alpha,
140140
/// Enum for crowdloan precompile
141141
Crowdloan,
142-
/// Pure proxy precompile
143-
PureProxy,
142+
/// Proxy precompile
143+
Proxy,
144144
/// Leasing precompile
145145
Leasing,
146146
}

0 commit comments

Comments
 (0)