Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
27 changes: 27 additions & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,33 @@ impl TryFrom<u8> for ProxyType {
}
}

impl TryInto<u8> for ProxyType {
type Error = ();

fn try_into(self) -> Result<u8, Self::Error> {
match self {
Self::Any => Ok(0),
Self::Owner => Ok(1),
Self::NonCritical => Ok(2),
Self::NonTransfer => Ok(3),
Self::Senate => Ok(4),
Self::NonFungible => Ok(5),
Self::Triumvirate => Ok(6),
Self::Governance => Ok(7),
Self::Staking => Ok(8),
Self::Registration => Ok(9),
Self::Transfer => Ok(10),
Self::SmallTransfer => Ok(11),
Self::RootWeights => Ok(12),
Self::ChildKeys => Ok(13),
Self::SudoUncheckedSetCode => Ok(14),
Self::SwapHotkey => Ok(15),
Self::SubnetLeaseBeneficiary => Ok(16),
Self::RootClaim => Err(()),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional? I thought we added root claim operations to proxy filter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The RootClaim in the ProxyType. But it is excluded in the impl TryFrom for ProxyType. So I think it is not convertible for this type. That is why I explicitly implement TryInto.

}
}
}
Comment on lines +192 to +217
Copy link
Collaborator

@l0r1s l0r1s Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is more idiomatic to avoid implementing TryInto directly but implement TryFrom instead because it provides a blanket (automatic) implementation of TryInto.

So if you implement TryFrom<ProxyType> for u8 you get TryInto<u8> for ProxyType for free.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check my reply above


impl Default for ProxyType {
// allow all Calls; required to be most permissive
fn default() -> Self {
Expand Down
36 changes: 36 additions & 0 deletions evm-tests/src/contracts/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,41 @@ export const IProxyABI = [
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "account",
"type": "bytes32"
}
],
"name": "getProxies",
"outputs": [
{
"components": [
{
"internalType": "bytes32",
"name": "delegate",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "proxy_type",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "delay",
"type": "uint256"
}
],
"internalType": "struct IProxy.ProxyInfo[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
}
];
63 changes: 55 additions & 8 deletions evm-tests/test/pure-proxy.precompile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ async function getProxies(api: TypedApi<typeof devnet>, address: string) {

describe("Test pure proxy precompile", () => {
const evmWallet = generateRandomEthersWallet();
// only used for edge case and normal proxy
const evmWallet2 = generateRandomEthersWallet();
const evmWallet3 = generateRandomEthersWallet();
const evmWallet4 = generateRandomEthersWallet();
const receiver = getRandomSubstrateKeypair();

let api: TypedApi<typeof devnet>
Expand All @@ -61,6 +63,7 @@ describe("Test pure proxy precompile", () => {
await forceSetBalanceToEthAddress(api, evmWallet.address)
await forceSetBalanceToEthAddress(api, evmWallet2.address)
await forceSetBalanceToEthAddress(api, evmWallet3.address)
await forceSetBalanceToEthAddress(api, evmWallet4.address)
})

it("Call createPureProxy, then use proxy to call transfer", async () => {
Expand Down Expand Up @@ -130,34 +133,78 @@ describe("Test pure proxy precompile", () => {
const proxies = await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet2.address))
const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet2)

const proxiesFromContract = await contract.getProxies(convertH160ToPublicKey(evmWallet2.address))
assert.equal(proxiesFromContract.length, proxies[0].length, "proxies length should be equal")

const type = 0;
const delay = 0;

const tx = await contract.addProxy(convertH160ToPublicKey(evmWallet3.address), type, delay)
await tx.wait()


const proxiesAfterAdd = await await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet2.address))
const proxiesList = proxiesAfterAdd[0].map(proxy => proxy.delegate)

const proxiesFromContractAfterAdd = await contract.getProxies(convertH160ToPublicKey(evmWallet2.address))

const length = proxiesAfterAdd[0].length
assert.equal(length, proxies[0].length + 1, "proxy should be set")
const proxy = proxiesAfterAdd[0][proxiesAfterAdd[0].length - 1]
assert.equal(proxiesFromContractAfterAdd.length, proxiesList.length, "proxy length should be equal")

assert.equal(proxy.delegate, convertH160ToSS58(evmWallet3.address), "proxy should be set")
for (let index = 0; index < proxiesFromContractAfterAdd.length; index++) {
const proxyInfo = proxiesFromContractAfterAdd[index]
let proxySs58 = convertPublicKeyToSs58(proxyInfo[0])
assert.ok(proxiesList.includes(proxySs58), "proxy should be set")
if (index === proxiesFromContractAfterAdd.length - 1) {
assert.equal(Number(proxyInfo[1]), type, "proxy_type should match")
assert.equal(Number(proxyInfo[2]), delay, "delay should match")
}
}

assert.equal(proxiesList.length, proxies[0].length + 1, "proxy should be set")
const proxy = proxiesList[proxiesList.length - 1]

assert.equal(proxy, convertH160ToSS58(evmWallet3.address), "proxy should be set")
const balance = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free

const amount = 1000000000;

const contract2 = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet3)


const callCode = await getTransferCallCode(api, receiver, amount)
const tx2 = await contract2.proxyCall(convertH160ToPublicKey(evmWallet2.address), [type], callCode)
await tx2.wait()

const balanceAfter = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free
assert.equal(balanceAfter, balance + BigInt(amount), "balance should be increased")
})

it("Call addProxy many times, then check getProxies is correct", async () => {
const proxies = await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet4.address))
const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet4)
assert.equal(proxies[0].length, 0, "proxies length should be 0")

const proxiesFromContract = await contract.getProxies(convertH160ToPublicKey(evmWallet4.address))
assert.equal(proxiesFromContract.length, proxies[0].length, "proxies length should be equal")

const type = 1;
const delay = 2;

for (let i = 0; i < 5; i++) {
const evmWallet = generateRandomEthersWallet()
const tx = await contract.addProxy(convertH160ToPublicKey(evmWallet.address), type, delay)
await tx.wait()
}

const proxiesAfterAdd = await await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet4.address))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the double await expected here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed one await.

const proxiesList = proxiesAfterAdd[0].map(proxy => proxy.delegate)

const proxiesFromContractAfterAdd = await contract.getProxies(convertH160ToPublicKey(evmWallet4.address))

assert.equal(proxiesFromContractAfterAdd.length, proxiesList.length, "proxy length should be equal")

for (let index = 0; index < proxiesFromContractAfterAdd.length; index++) {
const proxyInfo = proxiesFromContractAfterAdd[index]
let proxySs58 = convertPublicKeyToSs58(proxyInfo[0])
assert.ok(proxiesList.includes(proxySs58), "proxy should be set")
assert.equal(Number(proxyInfo[1]), type, "proxy_type should match")
assert.equal(Number(proxyInfo[2]), delay, "delay should match")
}
})
});
34 changes: 33 additions & 1 deletion precompiles/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ use frame_system::RawOrigin;
use pallet_evm::{AddressMapping, PrecompileHandle};
use pallet_subtensor_proxy as pallet_proxy;
use precompile_utils::EvmResult;
use sp_core::H256;
use sp_core::{H256, U256};
use sp_runtime::{
codec::DecodeLimit,
traits::{Dispatchable, StaticLookup},
};
use sp_std::boxed::Box;
use sp_std::vec;
use sp_std::vec::Vec;
use subtensor_runtime_common::ProxyType;
pub struct ProxyPrecompile<R>(PhantomData<R>);
Expand Down Expand Up @@ -239,4 +240,35 @@ where

handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id))
}

#[precompile::public("getProxies(bytes32)")]
#[precompile::view]
pub fn get_proxies(
_handle: &mut impl PrecompileHandle,
account_id: H256,
) -> EvmResult<Vec<(H256, U256, U256)>> {
let account_id = R::AccountId::from(account_id.0.into());

let proxies = pallet_proxy::pallet::Pallet::<R>::proxies(account_id);
let mut result: Vec<(H256, U256, U256)> = vec![];
for proxy in proxies.0 {
let delegate: [u8; 32] = proxy.delegate.into();
let proxy_type: u8 =
proxy
.proxy_type
.try_into()
.map_err(|_| PrecompileFailure::Error {
exit_status: ExitError::Other("Invalid proxy type".into()),
})?;
let delay: u32 = proxy
.delay
.try_into()
.map_err(|_| PrecompileFailure::Error {
exit_status: ExitError::Other("Invalid delay".into()),
})?;
result.push((delegate.into(), proxy_type.into(), delay.into()));
}

Ok(result)
}
}
160 changes: 160 additions & 0 deletions precompiles/src/solidity/proxy.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
[
{
"type": "function",
"name": "createPureProxy",
"inputs": [
{
"name": "proxy_type",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "delay",
"type": "uint32",
"internalType": "uint32"
},
{
"name": "index",
"type": "uint16",
"internalType": "uint16"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "proxyCall",
"inputs": [
{
"name": "real",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "force_proxy_type",
"type": "uint8[]",
"internalType": "uint8[]"
},
{
"name": "call",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "killPureProxy",
"inputs": [
{
"name": "spawner",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "proxy_type",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "index",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "height",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "ext_index",
"type": "uint32",
"internalType": "uint32"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "addProxy",
"inputs": [
{
"name": "delegate",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "proxy_type",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "delay",
"type": "uint32",
"internalType": "uint32"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "removeProxy",
"inputs": [
{
"name": "delegate",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "proxy_type",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "delay",
"type": "uint32",
"internalType": "uint32"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "removeProxies",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "pokeDeposit",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "getProxies",
"inputs": [
{
"name": "account",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [
{
"name": "",
"type": "bytes32[]",
"internalType": "bytes32[]"
}
],
"stateMutability": "view"
}
]
Loading
Loading