Gnosis Safe 多重签名,multisend

Gnosis Safe 多重签名,multisend,第1张

Gnosis Safe 功能流程 EIP712ABI创建保险箱,Safe合约用户自定义构造函数与转移资产用户批量构造交易,整合到一笔交易,multisend自己做了一个测试网站,方便理解abi,encode编码

EIP712

EIP712是一种签名标准,主要是针对明文。EIP712详细解释

EIP712签名的结构由三部分组成,分别是domainData,types,sign_message。
签名的原理就是拿用户的私钥,对一串32个字节的哈希值进行ECDSA算法计算,得出来一个65个字节的值,由r,s,v组成。
智能合约验签的原理就是拿到这个签名进行算法解析,接出来先是公钥,然后推出用户地址。这就是验签。
//基础准备工作,ethers.js或者web3.js都可以,这里拿ethers.js举例,直接前端引入,可以存储到本地 加载速度更快更安全,如果用外链cdn加速有时候可能会加载失败。
<script src="https://cdn.ethers.io/lib/ethers-5.6.9.umd.min.js"
    			type="application/javascript"></script>
    			
 const provider = new ethers.providers.Web3Provider(window.ethereum)//只要浏览器下载了小狐狸插件,window全局对象中都会有ethereum这个对象,如果是初学者,稍微百度一下window全局对象
 const accounts = await provider.send("eth_requestAccounts", [])//如果没有链接钱包会自动d出来小狐狸连接框,如果链接钱包了就没什么显示

 abiCoder = ethers.utils.defaultAbiCoder//ethers.js自带的编码器,就是用来编码的,编码就是把要交易的信息打包成一个类似string的东西,在合约中为bytes,0x十六进制开头

 var signer_712 = provider.getSigner()//这个signer_712变量其实是拿到了当前链接小狐狸的账号,只包含一个用户地址,不包含多个。
 
const domainData = {
                    chainId: 4,//这个是int类型的,指定链id,很重要,比如ETH主网就必须是1否则d不出来小狐狸,是对应上的。
                    verifyingContract: "0x7Fbf984cd60d763Eb94C4C466013f1Ee90dd78Cf",//这个是Safe保险箱合约地址
                    };

const types = {     SafeTx:  //这里要和合约匹配上,gnosis safe其中rinkeby一个合约地址为0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552,可以去观察SafeTx这里的细节
                                                             [{ name: 'to',type: 'address'},
                                                              { type: 'uint256', name: 'value' },
                                                              { type: 'bytes', name: 'data' },
                                                              { type: 'uint8', name: 'operation' },
                                                              { type: 'uint256', name: 'safeTxGas' },
                                                              { type: 'uint256', name: 'baseGas' },
                                                              { type: 'uint256', name: 'gasPrice' },
                                                              { type: 'address', name: 'gasToken' },
                                                              { type: 'address', name: 'refundReceiver' },
                                                              { type: 'uint256', name: 'nonce' }]}//这个只是定义所有消息数据的类型。
var sign_message = {
                        to:     sign_to_address,//类似0x40A2aCCbd92BCA938b02010E17A5b8929b49130D
                        value:  0,
                        data:   total_multi_data,//类似0x8d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000013200d1d8a3af43788876089b57a214544f9fff9ed08500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000f94e4af78b3f222fa0f8be0f47b6ec6960866e0b0000000000000000000000000000000000000000000000004279842b41585000
                        operation:   1,//或者0,  0表示call,1表示delegatecall,这个需要一定基础的消化的
                        safeTxGas:  0,
                        baseGas:     0,
                        gasPrice:    0,
                        gasToken:    '0x0000000000000000000000000000000000000000',
                        refundReceiver:   "0x0000000000000000000000000000000000000000",
                        nonce:      sign_nonce ,//类似17,18 在gnosis 保险箱合约中 表示交易提交的次数
                    };
var signature = await signer_712._signTypedData(domainData, types, sign_message)//这里的signature就是此用户签名信息。用户签名之后,需要把这个签名信息向后端发起接口请求,让后端中心化服务器存储的。
ABI ABI是前端与区块链智能合约链接的接口,特别重要。 ABI介绍及解释
ABI全名:Application Binary Interface,应用二进制接口文件。智能合约的接口描述,描述了字段名称、字段类型、方法名称、参数名称、参数类型、方法返回值类型等。
当合约被编译后,对应的abi文件也就确定了。
在前端代码中,大部分用abi无非就两种,一个是前端构造合约实例中,参数带abi文件,这个abi是已经发布的合约中自带的。一种是abi,encode的这种工具。例如,
var contracts_gnosis = {
	gnosis_factory:{
		address:'0xa6b71e26c5e0845f74c812102ca7114b6a896ab2',
		abi:[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract GnosisSafeProxy","name":"proxy","type":"address"},{"indexed":false,"internalType":"address","name":"singleton","type":"address"}],"name":"ProxyCreation","type":"event"},{"inputs":[{"internalType":"address","name":"_singleton","type":"address"},{"internalType":"bytes","name":"initializer","type":"bytes"},{"internalType":"uint256","name":"saltNonce","type":"uint256"}],"name":"calculateCreateProxyWithNonceAddress","outputs":[{"internalType":"contract GnosisSafeProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"singleton","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"createProxy","outputs":[{"internalType":"contract GnosisSafeProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_singleton","type":"address"},{"internalType":"bytes","name":"initializer","type":"bytes"},{"internalType":"uint256","name":"saltNonce","type":"uint256"},{"internalType":"contract IProxyCreationCallback","name":"callback","type":"address"}],"name":"createProxyWithCallback","outputs":[{"internalType":"contract GnosisSafeProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_singleton","type":"address"},{"internalType":"bytes","name":"initializer","type":"bytes"},{"internalType":"uint256","name":"saltNonce","type":"uint256"}],"name":"createProxyWithNonce","outputs":[{"internalType":"contract GnosisSafeProxy","name":"proxy","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"proxyCreationCode","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"proxyRuntimeCode","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"pure","type":"function"}]
        }
}
var contract_gnosis_factory = new ethers.Contract(contracts_gnosis.gnosis_factory.address,contracts_gnosis.gnosis_factory.abi,signer_create);
//contracts_gnosis.gnosis_factory.abi就包含了很多个函数,是列表里面带json格式,大约长这样,
//[{"inputs":[{"internalType":"address","name":"_singleton","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"stateMutability":"payable","type":"fallback"}]
//他其实也可以自由定义,可以增加,也可以删减,就是一个接口。
await contract_gnosis_factory.createProxyWithNonce(gnosis_safe_address,gnosisSafeData_final,salt); 
}

创建保险箱,Safe合约 这是Gnosis safe最开始的功能,创建保险箱合约。用户点击创建按钮,提交DAO组织用户们的地址和阈值,就可以创建保险箱合约了。保险箱合约的用处就是主要是管理共同资产,然后代替DAO成员意向执行共同决策的。



async function CreateSafe(){  //创建保险箱合约
    var ethereum = window.ethereum;//只要浏览器下载了小狐狸插件,window全局对象中都会有ethereum这个对象。
    var provider = new ethers.providers.Web3Provider(window.ethereum)//把ethers的provider设置成小狐狸钱包的环境。

    await provider.send("eth_requestAccounts", []); //如果没有链接钱包会自动d出来链接框,如果链接钱包了则什么事也不干。

    var signer_create =  provider.getSigner()//这个signer_create变量其实是拿到了当前链接小狐狸的账号,只包含一个用户地址,不包含多个。

    abiCoder = ethers.utils.defaultAbiCoder//ethers.js自带的编码器,就是用来编码的,编码就是把要交易的信息打包成一个类似string的东西,在合约中为bytes,0x十六进制开头


    var contract_gnosis_factory = new ethers.Contract(contracts_gnosis.gnosis_factory.address,contracts_gnosis.gnosis_factory.abi,signer_create);//这里的功能是链接合约,生成合约对象了,最后的signer_create参数是当前部署者地址的对象,也就是说返回的contract_gonsis_factory这个对象已经具备了很强大的和合约交互的功能了。


    var safeAccounts = [
             '0x4d2E1A38d07Eadf5C62CfDaF93547DAe09F1EF83',
             '0x592916d0D7fcaec0A0A0504134364721Aafd5e87',
             '0xF94e4af78b3f222fa0F8Be0F47b6Ec6960866E0b',
         ]//这里我测试是写死的,其实这个不应该是写死的,是最开始的DAO创始人去设置的。在前端中到时候可以写一个列表,前端接受用户输入的地址,直接保存就可以,等创建保险箱成功后,再去把信息反馈给后端。

    var numConfirmations = 1;这个是阈值,就是DAO中有几个人同意就可以执行交易,如果这里是1,那么团队中只要有一个人签名就可以执行了。

    var ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';这个是0地址,要满足gnosis合约规定方式

    var EMPTY_DATA = '0x';//这个是0字节,要满足gnosis合约规定方式

    var fallbackHandler_address = '0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4';//gnosis好像意思是这个是fallback处理的地址,我感觉没什么用,在合约交互的时候没走过这个合约。
                                                                               
    var gnosisSafeData = abiCoder.encode(['address[]','uint256','address','bytes','address','address','uint256','address'],
                        [safeAccounts,
                        numConfirmations,
     					ZERO_ADDRESS,
     					EMPTY_DATA,
     					fallbackHandler_address,
     					ZERO_ADDRESS,
     					0,
     					ZERO_ADDRESS]);//这个就是编码(其实编码就是压缩,没有很多其他的 *** 作),把这些东西压缩编码一下,
    var gnosisSafeData_final = '0xb63e800d'+gnosisSafeData.substring(2);//0xb63e800d=web3.eth.abi.encodeFunctionSignature('myMethod(uint256,string)'),这个就是在合约字节码中找函数的对应的位置
    var gnosis_safe_address = "0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552";//这个是rinkeby测试网指定的一个主合约地址,写死。
    var salt = 1657266633780;//这个其实是时间戳,是要变化的,也是传入调用合约函数的最后一个参数,具体值是当前的时间,date.now()

    await contract_gnosis_factory.createProxyWithNonce(gnosis_safe_address,gnosisSafeData_final,salt);//这个是ethers调用合约的标准方法,createProxyWithNonce这个是合约中定义的函数名,其实他这里是从ABI上找的,记得刚开始contract_gnosis_factory的变量中带了abi的,如果那个abi没有这个方法,小狐狸钱包都起不来。
}
用户自定义构造函数与转移资产

1>用户自定义构造函数
这里的构造函数更应该说是指定某些函数功能交互,就是任意合约的任意函数(可执行),哪怕不是转账任意的都行。
Safe保险箱内的用户可以构造任意的链上交易,意思就是比如这条链上随便有个NFT合约,有个投票合约,有个质押合约,我让这个Safe保险箱执行指定合约一个可执行函数都是可以的。


var contracts_gnosis_safe = {
	gnosis:{
		address:'0x7Fbf984cd60d763Eb94C4C466013f1Ee90dd78Cf',
		abi:[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"AddedOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"approvedHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"owner","type":"address"}],"name":"ApproveHash","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"handler","type":"address"}],"name":"ChangedFallbackHandler","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"guard","type":"address"}],"name":"ChangedGuard","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"threshold","type":"uint256"}],"name":"ChangedThreshold","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"module","type":"address"}],"name":"DisabledModule","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"module","type":"address"}],"name":"EnabledModule","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"txHash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"payment","type":"uint256"}],"name":"ExecutionFailure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"}],"name":"ExecutionFromModuleFailure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"module","type":"address"}],"name":"ExecutionFromModuleSuccess","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"txHash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"payment","type":"uint256"}],"name":"ExecutionSuccess","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"RemovedOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SafeReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"initiator","type":"address"},{"indexed":false,"internalType":"address[]","name":"owners","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"threshold","type":"uint256"},{"indexed":false,"internalType":"address","name":"initializer","type":"address"},{"indexed":false,"internalType":"address","name":"fallbackHandler","type":"address"}],"name":"SafeSetup","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"msgHash","type":"bytes32"}],"name":"SignMsg","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"addOwnerWithThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hashToApprove","type":"bytes32"}],"name":"approveHash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"approvedHashes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"changeThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"dataHash","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"bytes","name":"signatures","type":"bytes"},{"internalType":"uint256","name":"requiredSignatures","type":"uint256"}],"name":"checkNSignatures","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"dataHash","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"bytes","name":"signatures","type":"bytes"}],"name":"checkSignatures","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"prevModule","type":"address"},{"internalType":"address","name":"module","type":"address"}],"name":"disableModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"}],"name":"enableModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address","name":"refundReceiver","type":"address"},{"internalType":"uint256","name":"_nonce","type":"uint256"}],"name":"encodeTransactionData","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address payable","name":"refundReceiver","type":"address"},{"internalType":"bytes","name":"signatures","type":"bytes"}],"name":"execTransaction","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"execTransactionFromModule","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"execTransactionFromModuleReturnData","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"start","type":"address"},{"internalType":"uint256","name":"pageSize","type":"uint256"}],"name":"getModulesPaginated","outputs":[{"internalType":"address[]","name":"array","type":"address[]"},{"internalType":"address","name":"next","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOwners","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"offset","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"getStorageAt","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getThreshold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"},{"internalType":"uint256","name":"safeTxGas","type":"uint256"},{"internalType":"uint256","name":"baseGas","type":"uint256"},{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"address","name":"gasToken","type":"address"},{"internalType":"address","name":"refundReceiver","type":"address"},{"internalType":"uint256","name":"_nonce","type":"uint256"}],"name":"getTransactionHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"}],"name":"isModuleEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"prevOwner","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"_threshold","type":"uint256"}],"name":"removeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"enum Enum.Operation","name":"operation","type":"uint8"}],"name":"requiredTxGas","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"handler","type":"address"}],"name":"setFallbackHandler","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"guard","type":"address"}],"name":"setGuard","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_owners","type":"address[]"},{"internalType":"uint256","name":"_threshold","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"address","name":"fallbackHandler","type":"address"},{"internalType":"address","name":"paymentToken","type":"address"},{"internalType":"uint256","name":"payment","type":"uint256"},{"internalType":"address payable","name":"paymentReceiver","type":"address"}],"name":"setup","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"signedMessages","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"targetContract","type":"address"},{"internalType":"bytes","name":"calldataPayload","type":"bytes"}],"name":"simulateAndRevert","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"prevOwner","type":"address"},{"internalType":"address","name":"oldOwner","type":"address"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"swapOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]
        }
keccak_function_value = getFunctionSeletor(abi_single)//这个是函数选择器,生成是8位16进制。类似0x1a19f89
document.getElementById("keccak_function").innerHTML = keccak_function_value;

var encode_para_data =  abiCoder.encode(matchObj_copy,matchObj_paras);//这个是对交易数据进行encode编码
//举个例子,abiCoder.encode(['address', 'address', 'uint256'],
					//[['0x4d2E1A38d07Eadf5C62CfDaF93547DAe09F1EF83', '0x592916d0D7fcaec0A0A0504134364721Aafd5e87', 6000]])
//encode_para_data = 0x0000000000000000000000004d2e1a38d07eadf5c62cfdaf93547dae09f1ef83000000000000000000000000592916d0d7fcaec0a0a0504134364721aafd5e870000000000000000000000000000000000000000000000000000000000001770
//encode都是32个字节0x20, 64位一个slot(插槽)进行编码,encodePacked并不按照32个字节(64位16进制)进行编码。
//这里是不带函数选择器的,类似0x1a19f89

var single_final =  keccak_function_value + encode_para_data.substring(2);//把完整的交易打包,完整的交易由函数选择器+参数组成,keccak_function_value这个是函数选择器,类似长这个样子0x1a19f89,mencode_para_data.substring(2)这个是压缩后的参数。
//keccak_function_value类似为0x1a19f894
//single_final的值类似为0x1a19f8940000000000000000000000004d2e1a38d07eadf5c62cfdaf93547dae09f1ef83000000000000000000000000592916d0d7fcaec0a0a0504134364721aafd5e870000000000000000000000000000000000000000000000000000000000001770
//0x1a19f894 														//4个字节,8位也就是长度为8的16进制
//0000000000000000000000004d2e1a38d07eadf5c62cfdaf93547dae09f1ef83  //一个solt,32个字节,64位16进制,合约函数中表示from
//000000000000000000000000592916d0d7fcaec0a0a0504134364721aafd5e87  //一个solt,32个字节,64位16进制,合约函数中表示to
//0000000000000000000000000000000000000000000000000000000000001770  //这个是value,					  1*16**3+7*16**2+7*16=4096+1792+112=6000
//这就是一笔交易的函数信息了

single_data = single_final//如果对一笔交易来讲,这个single_final就是最后要签名的数据中的data了。(这个single_data只是你要签名信息sign_message中的一部分,是sign_message的第三个参数)


var dom_address = document.getElementById('Contract_address').value;//这里sign_to_address 是指你要执行的这个函数是哪个合约里的,这里要填写合约地址。


sign_to_address = dom_address;



sign_message = {
                  to:     sign_to_address,
                  value:  0,
                  data:   single_data,
                  operation:   0,
                  safeTxGas:  0,
                  baseGas:     0,
                  gasPrice:    0,
                  gasToken:    '0x0000000000000000000000000000000000000000',
                  refundReceiver:   "0x0000000000000000000000000000000000000000",
                  nonce:      sign_nonce ,
              };//这是最后要签名的信息sign_message



  var contracts_safe_execTransaction = new ethers.Contract(contracts_gnosis_safe.gnosis.address,contracts_gnosis_safe.gnosis.abi,signer_ExecTransaction)
              var to = sign_to_address;
              var valueInWei = 0;
              var data = sign_single_data;
              var operation = 0;
              var safeTxGas = 0;
              var baseGas = 0;
              var gasPrice = 0;
              var gasToken = "0x0000000000000000000000000000000000000000";
              var refundReceiver = "0x0000000000000000000000000000000000000000";
              var sigs = sig_s;//这些变量就是拿到最后要执行合约交易的参数,contracts_safe_execTransaction这个是合约对象,已经包含了用户要执行合约的地址,其余的变量都是要传入的参数。


            try{

            await contracts_safe_execTransaction.execTransaction(to,valueInWei,data,operation,safeTxGas,baseGas,gasPrice,gasToken,refundReceiver,sigs);//这块就是要唤醒小狐狸执行交易合约的代码

               alert('success')
               nonce ++;
            }
            catch{
                alert('执行交易失败')
            }//其实真正这个合约中只有一个fallback函数,rinkeby上地址为0x7Fbf984cd60d763Eb94C4C466013f1Ee90dd78Cf,这就是我自己的保险箱Safe合约,没有这个execTransaction函数,在创建保险箱的时候走的是0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2这个合约,但是Safe合约里fallback函数里面用了内联汇编就可以让他指向内部的singleton合约地址,地址为0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552,让这个singleton合约去验证多重签名和执行其他合约的函数,是这个意思。但是如果在构造合约实例的时候,配置的abi中没有这个execTransaction函数是会报错的。

2>转移资产

可以理解成,转移资产和用户自己构造交易在合约层面是一模一样的,都是一样的步骤。区别就是一个是用户自定义指定合约与函数,需要他自己填写函数的参数。转移资产功能只需要用户填写to地址(给谁转账),以及对应的资产数量。转移资产这种方式其实是前端提前写好的。转移资产基本都是走transfer函数,从Safe合约中转给to地址,from是Safe合约。用户只需要添加to地址与数量,转移资产功能可以转ETH主币也可以转ERC20,721,1155。
如果是转ETH,同理,用户需要添加to地址以及对应的数量,前端去动态encode生成data(接受ETH value数量)。

用户批量构造交易,整合到一笔交易,multisend

*1>每一次用户自定义构造交易
2>encodePacked和encode *** 作是类似的,只不过encodePacked在encode基础上针对参数类型进行了缩放。比如encodePacked(uint8),传入参数是1,那么得出的结果就是0x01,如果是encodePacked(uint16),传入参数也是1的话,得出结果是0x0001,但是encode的话,全部都是64位16进制,得出的结果是0x0000000000000000000000000000000000000000000000000000000000000001.
3>总体的流程是,用户自定义构造多笔交易,这一步只需要encode,再进行encodePacked成完整的一笔交易,然后把这个完整的每笔encodePacked之后交易一个个拼接,组成最后的多笔交易的final_multi_data。(比如用户要构造三笔交易,他先构造了一笔,前端做的事就是先encode这个data,然后把data和其他的参数一起进行encodePacked,得到对应的信息,然后构造第二笔也是一样的 *** 作,接着把上笔完整交易encodePacked后返回的东西和这笔完整交易encodePacked后返回的东西进行直接拼接。
最后对总的信息再进行encode成bytes类型。

var uint8_operation = document.getElementById('operation').value//这个是用户在执行交易对这个合约的 *** 作,是0或者1,暂时不用管,都写0,0在gnosis合约中表示call,1在gnosis合约定义位delegatecall
var address_to = document.getElementById('to').value//这个合约函数要执行的时候指定的合约地址,比如想法是在A合约里面有个f1函数,执行A合约里面的f1函数,我不能用f1函数去指定B合约。类似0xd1d8a3af43788876089b57a214544f9fff9ed085
var uint256_value = document.getElementById('value').value * 10 ** 18//这个是转移eth的数量,这里10 **18 是精度,就是如果页面中直接给了一个值5000,如果不带这个10**18,结果会是0.000000000000005个ETH。
var uint_data_length = ((single_data.length)-2)/2//这里是算单次交易转换成data的字节长度。2位16进制=1个字节,-2是把0x去掉,/2是拿到字节长度,而不是位长度。
var single_data = keccak_function_value + encode_para_data.substring(2)
//这single_data是单笔交易,不包含operation,函数所在的合约地址,转账ETH金额
//其中keccak_function_value是函数选择器,类似0x1a19f894
//encode_para_data 类似0x0000000000000000000000004d2e1a38d07eadf5c62cfdaf93547dae09f1ef83000000000000000000000000592916d0d7fcaec0a0a0504134364721aafd5e870000000000000000000000000000000000000000000000000000000000001770,substring(2)是把前两位0x去掉

var single_total_OAV = ethers.utils.solidityPack(["uint8", "address", "uint256","uint256", "bytes"], //ethers.utils.solidityPack就是encodePacked功能
    [uint8_operation,
    address_to,
    uint256_value,
    uint_data_length,
    single_data])
    console.log('single_total_OAV',single_total_OAV)
    multi_data_final = multi_data_final + single_total_OAV.substring(2)//multi_data_final 最开始是"0x"
    //multi_data_final 才是最后想要拿到的一次性执行多笔交易的完整的data,但是还需要最后再encode成bytes类型一下。
    //single_total_OAV类似长这样:0x00d1d8a3af43788876089b57a214544f9fff9ed085000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000641a19f8940000000000000000000000004d2e1a38d07eadf5c62cfdaf93547dae09f1ef83000000000000000000000000592916d0d7fcaec0a0a0504134364721aafd5e870000000000000000000000000000000000000000000000000000000000001770
    //把single_total_OAV的值分离开来就是
    //00    															 //uint8 encodePacked之后的值为0,0表示call,1表示delegatecall 。 值为uint8_operation
    //d1d8a3af43788876089b57a214544f9fff9ed085           				 //这个表示用户自定义想执行的哪个合约地址 ,值为address_to
    //0000000000000000000000000000000000000000000000000000000000000000   //32字节,表示转多少wei的ETH
    //0000000000000000000000000000000000000000000000000000000000000064   //32字节, 64表示 16*6+4=100,也就是交易数据带函数选择器一共是100个字节,比如交易数据就是
    //1a19f894   														 //4个字节,函数选择器
	//0000000000000000000000004d2e1a38d07eadf5c62cfdaf93547dae09f1ef83   //32个字节,这个函数中表示from地址
	//000000000000000000000000592916d0d7fcaec0a0a0504134364721aafd5e87   //32个字节,这个函数中表示to地址
	//0000000000000000000000000000000000000000000000000000000000001770   //32个字节,这个函数中表示转账的数量
	// 从1a19f894一直到最后的1770就是完整的一个交易数据,4+32*3=100 个字节, 16*6+4=100个字节。


var final_encode_multi_data = abiCoder.encode(['bytes'],
                                              [multi_data_final]);
//把multi_data_final encode成bytes类型的                
total_multi_data = '0x8d80ff0a' + final_encode_multi_data.substring(2)//这个total_multi_data 是最后multisend EIP712签名的那个data,这个才是最终的,其他的所有encode编码都是为这个data服务。指向的是固定的一个合约,帮你执行这些交易,函数也是固定的所以这里直接固定0x8d80ffoa就好,0x8d80ffoa的来源就是keccak256( function multiSend(bytes memory transactions)),然后去bytes4,取前4个字节,前8位16进制,因为哈希过后是固定长度的32字节。



var contracts_safe_execTransaction = new ethers.Contract(contracts_gnosis_safe.gnosis.address,contracts_gnosis_safe.gnosis.abi,signer_ExecTransaction)
var to = sign_to_address;  //如果这步是执行multisend的话,只能是前端自己去配置,比如rinkeby上的合约地址就是0x40A2aCCbd92BCA938b02010E17A5b8929b49130D,不同的网络要配置不同的合约,合约里面必须用内联汇编把交易分离,有时间我会讲一下内联汇编是如何通过evm code把data分离,我会着重讲内存布局
var valueInWei = 0;
var data = total_multi_data;   //这里要传入合约参数第三个参数就是data,值为total_multi_data
var operation = 1;
var safeTxGas = 0;
var baseGas = 0;
var gasPrice = 0;
var gasToken = "0x0000000000000000000000000000000000000000";
var refundReceiver = "0x0000000000000000000000000000000000000000";
var sigs = sig_s;//这里假设用户都签完名了

//这个就是执行最后交易唤醒小狐狸的函数方法,最关键的就是data,然后传入对应解析data的sign_to_address这个合约地址, *** 作operation为1,和用户验签后返回的signature,65字节*DAO成员通过人数阈值的一串bytes,变成sigs,把sigs带入调用合约中的最后一个参数,就可以执行多个合约多个函数的交易了。

await contracts_safe_execTransaction.execTransaction(to,valueInWei,data,operation,safeTxGas,baseGas,gasPrice,gasToken,refundReceiver,sigs);
自己做了一个测试网站,方便理解abi,encode编码

我自己有一个网站,主要是通过ABI产生data值的,网站为:http://114.116.97.234/Gnosis/eip712_gnosis_ethers

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/2991875.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-23
下一篇 2022-09-23

发表评论

登录后才能评论

评论列表(0条)

保存