Closed smitea closed 2 years ago
I'm looking for a solution like this. I want to use WyvernAtomicizer.atomicize
for eth transfer (in the getTransferData
function code), but it's sender is a proxy.
contract('WyvernExchange', (accounts) => {
let deploy_core_contracts = async () => {
let [registry, atomicizer] = await Promise.all([WyvernRegistry.new(), WyvernAtomicizer.new()])
let [exchange, statici] = await Promise.all([WyvernExchange.new(CHAIN_ID, [registry.address], '0x'), WyvernStatic.new(atomicizer.address)])
await registry.grantInitialAuthentication(exchange.address)
return { registry, exchange: wrap(exchange), atomicizer, statici }
}
let deploy = async contracts => Promise.all(contracts.map(contract => contract.new()))
const getTransferData = async (from, to, amount, coin) => {
const value = amount
// ERC20 transfer
if (coin) {
const erc20 = await getERC20(coin);
const data = erc20.methods.transferFrom(from, to, value).encodeABI()
return { data, addr: coin.addr, value: 0, len: (data.length - 2) / 2 }
}
// ETH transfer
return { data: "0x", addr: to, value, len: 0, }
}
const getERC20 = async (coin) => {
if (!coin) return null;
let [erc20] = await deploy([TestERC20])
return erc20;
}
const any_erc1155_for_erc20_test = async (options) => {
const {
tokenId,
sellAmount,
sellingPrice,
buyAmount,
account_a,
account_b,
account_c,
account_d,
royalty,
commission,
} = options
let { exchange, registry, statici, atomicizer } = await deploy_core_contracts()
let [erc20, erc1155] = await deploy([TestERC20, TestERC1155])
let totalMintAmount = buyAmount * sellingPrice
await erc20.mint(account_b, totalMintAmount)
await erc1155.mint(account_a, tokenId, sellAmount)
await registry.registerProxy({ from: account_a })
let proxy1 = await registry.proxies(account_a)
assert.equal(true, proxy1.length > 0, 'no proxy address for account a')
await registry.registerProxy({ from: account_b })
let proxy2 = await registry.proxies(account_b)
assert.equal(true, proxy2.length > 0, 'no proxy address for account b')
await erc1155.setApprovalForAll(proxy1, true, { from: account_a })
const abi = [{ 'constant': false, 'inputs': [{ 'name': 'addrs', 'type': 'address[]' }, { 'name': 'values', 'type': 'uint256[]' }, { 'name': 'calldataLengths', 'type': 'uint256[]' }, { 'name': 'calldatas', 'type': 'bytes' }], 'name': 'atomicize', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }]
const atomicizerc = new web3.eth.Contract(abi, atomicizer.address)
let tradingAmount = buyAmount * sellingPrice
let commissionAmount = commission * tradingAmount
let royaltyAmount = royalty * (tradingAmount - commissionAmount)
let finalAmount = tradingAmount - commissionAmount - royaltyAmount
console.log("tradingAmount: %d", tradingAmount)
console.log("commissionAmount: %d", commissionAmount)
console.log("royaltyAmount: %d", royaltyAmount)
console.log("finalAmount: %d", finalAmount)
const erc1155c = new web3.eth.Contract(erc1155.abi, erc1155.address)
const erc20c = new web3.eth.Contract(erc20.abi, erc20.address)
const coin = null;
const selectorOne = web3.eth.abi.encodeFunctionSignature('anyAddOne(bytes,address[7],uint8[2],uint256[6],bytes,bytes)')
const selectorTwo = web3.eth.abi.encodeFunctionSignature('anyAddOne(bytes,address[7],uint8[2],uint256[6],bytes,bytes)')
const params1 = web3.eth.abi.encodeParameters(
['address[2]', 'uint256[3]'],
[[erc1155.address, atomicizer.address], [tokenId, buyAmount, sellingPrice]]
)
const params2 = web3.eth.abi.encodeParameters(
['address[2]', 'uint256[3]'],
[[atomicizer.address, erc1155.address], [tokenId, sellingPrice, buyAmount]]
)
const one = {
registry: registry.address,
maker: account_a,
staticTarget: statici.address,
staticSelector: selectorOne,
staticExtradata: params1,
maximumFill: sellAmount,
listingTime: '0',
expirationTime: '10000000000',
salt: '11'
}
const two = {
registry: registry.address,
maker: account_b,
staticTarget: statici.address,
staticSelector: selectorTwo,
staticExtradata: params2,
maximumFill: sellingPrice * buyAmount,
listingTime: '0',
expirationTime: '10000000000',
salt: '12'
}
const firstData = erc1155c.methods.safeTransferFrom(
account_a,
account_b,
tokenId,
buyAmount,
"0x"
).encodeABI()
let params = []
if (finalAmount !== 0) {
params.push(await getTransferData(account_b, account_a, finalAmount, coin));
}
if (commissionAmount !== 0) {
params.push(await getTransferData(account_b, account_c, commissionAmount, coin));
}
if (royaltyAmount !== 0) {
params.push(await getTransferData(account_b, account_d, royaltyAmount, coin));
}
let addrs = [], values = [], calldataLengths = [];
let calldatas = ''
for (let i = 0; i < params.length; i++) {
const data = params[i]
addrs.push(data.addr)
values.push(data.value)
calldataLengths.push(data.len)
calldatas += data.data.slice(2)
}
const secondData = atomicizerc.methods.atomicize(addrs, values, calldataLengths, '0x' + calldatas).encodeABI()
const firstCall = { target: erc1155.address, howToCall: 0, data: firstData }
const secondCall = { target: atomicizer.address, howToCall: 1, data: secondData }
let sigOne = await exchange.sign(one, account_a)
let sigTwo = await exchange.sign(two, account_b)
await exchange.atomicMatchWith(
one,
sigOne,
firstCall,
two,
sigTwo,
secondCall,
ZERO_BYTES32,
{ from: account_b }
)
let [
account_a_erc1155_balance,
account_a_erc20_balance,
account_b_erc20_balance,
account_c_erc20_balance,
account_d_erc20_balance,
account_b_erc1155_balance
] = await Promise.all([
erc1155.balanceOf(account_a, tokenId),
erc20.balanceOf(account_a),
erc20.balanceOf(account_b),
erc20.balanceOf(account_c),
erc20.balanceOf(account_d),
erc1155.balanceOf(account_b, tokenId)
])
console.log("account_a balance: %d, erc1155: %s", account_a_erc20_balance.toNumber(), account_a_erc1155_balance.toNumber())
console.log("account_b balance: %d, erc1155: %s", account_b_erc20_balance.toNumber(), account_b_erc1155_balance.toNumber())
console.log("account_c balance: %d", account_c_erc20_balance.toNumber())
console.log("account_d balance: %d", account_d_erc20_balance.toNumber())
assert.equal(account_a_erc20_balance.toNumber(), finalAmount, 'Incorrect ERC20 balance from account A')
assert.equal(account_b_erc20_balance.toNumber(), 0, 'Incorrect ERC20 balance from account B')
assert.equal(account_c_erc20_balance.toNumber(), commissionAmount, 'Incorrect ERC20 balance from account C')
assert.equal(account_d_erc20_balance.toNumber(), royaltyAmount, 'Incorrect ERC20 balance from account D')
assert.equal(account_b_erc1155_balance.toNumber(), buyAmount, 'Incorrect ERC1155 balance from account B')
assert.equal(account_a_erc1155_balance.toNumber(), sellAmount - buyAmount, 'Incorrect ERC1155 balance from account B')
}
it('StaticMarket: matches erc1155 <> erc20 order, 1 fill', async () => {
const price = 10000
const mintAmount = 2
return any_erc1155_for_erc20_test({
tokenId: 1,
sellAmount: mintAmount,
sellingPrice: price,
buyAmount: 1,
account_a: accounts[0],
account_b: accounts[1],
account_c: accounts[2],
account_d: accounts[3],
royalty: 0.2,
commission: 0.025,
})
})
})
@smitea hop into the Wyvern Discord: https://discord.com/invite/weUTpah286
Ok, for anyone interested in how this worked out in the end, below is a sample code that works. However, it involves a new predicate
transferERC20ExactTo
which gets the fee recipient via the extra data.Click for code
```typescript it("erc721 <> erc20 with checks", async () => { const alice = accounts[0]; const bob = accounts[1]; const carol = accounts[2]; const david = accounts[3]; const { atomicizer, exchange, registry, statici } = await deployCoreContracts(); const [erc20, erc721] = await deploy([TestERC20, TestERC721]); const abi = [ { constant: false, inputs: [ { name: "addrs", type: "address[]" }, { name: "values", type: "uint256[]" }, { name: "calldataLengths", type: "uint256[]" }, { name: "calldatas", type: "bytes" }, ], name: "atomicize", outputs: [], payable: false, stateMutability: "nonpayable", type: "function", }, ]; const atomicizerc = new web3.eth.Contract(abi, atomicizer.address); await registry.registerProxy({ from: alice }); const aliceProxy = await registry.proxies(alice); assert.equal(true, aliceProxy.length > 0, "No proxy address for Alice"); await registry.registerProxy({ from: bob }); const bobProxy = await registry.proxies(bob); assert.equal(true, bobProxy.length > 0, "No proxy address for Bob"); const amount = 1000; const fee1 = 10; const fee2 = 20; const tokenId = 0; await Promise.all([ erc20.mint(bob, amount + fee1 + fee2), erc721.mint(alice, tokenId), ]); await Promise.all([ erc20.approve(bobProxy, amount + fee1 + fee2, { from: bob }), erc721.setApprovalForAll(aliceProxy, true, { from: alice }), ]); const erc20c = new web3.eth.Contract(erc20.abi, erc20.address); const erc721c = new web3.eth.Contract(erc721.abi, erc721.address); let selectorOne, extradataOne; { const selector = web3.eth.abi.encodeFunctionSignature( "split(bytes,address[7],uint8[2],uint256[6],bytes,bytes)" ); // Call should be an ERC721 transfer const selectorCall = web3.eth.abi.encodeFunctionSignature( "transferERC721Exact(bytes,address[7],uint8,uint256[6],bytes)" ); const extradataCall = web3.eth.abi.encodeParameters( ["address", "uint256"], [erc721.address, tokenId] ); // Countercall should include an ERC20 transfer const selectorCountercall = web3.eth.abi.encodeFunctionSignature( "sequenceAnyAfter(bytes,address[7],uint8,uint256[6],bytes)" ); const countercallSelector1 = web3.eth.abi.encodeFunctionSignature( "transferERC20Exact(bytes,address[7],uint8,uint256[6],bytes)" ); const countercallExtradata1 = web3.eth.abi.encodeParameters( ["address", "uint256"], [erc20.address, amount] ); const extradataCountercall = web3.eth.abi.encodeParameters( ["address[]", "uint256[]", "bytes4[]", "bytes"], [ [statici.address], [(countercallExtradata1.length - 2) / 2], [countercallSelector1], countercallExtradata1, ] ); const params = web3.eth.abi.encodeParameters( ["address[2]", "bytes4[2]", "bytes", "bytes"], [ [statici.address, statici.address], [selectorCall, selectorCountercall], extradataCall, extradataCountercall, ] ); selectorOne = selector; extradataOne = params; } const one = { registry: registry.address, maker: alice, staticTarget: statici.address, staticSelector: selectorOne, staticExtradata: extradataOne, maximumFill: 1, listingTime: "0", expirationTime: "10000000000", salt: "11", }; const sigOne = await exchange.sign(one, alice); let selectorTwo, extradataTwo; { const selector = web3.eth.abi.encodeFunctionSignature( "split(bytes,address[7],uint8[2],uint256[6],bytes,bytes)" ); // Call should be an ERC20 transfer to recipient + fees const selectorCall = web3.eth.abi.encodeFunctionSignature( "sequenceExact(bytes,address[7],uint8,uint256[6],bytes)" ); const callSelector1 = web3.eth.abi.encodeFunctionSignature( "transferERC20Exact(bytes,address[7],uint8,uint256[6],bytes)" ); const callExtradata1 = web3.eth.abi.encodeParameters( ["address", "uint256"], [erc20.address, amount] ); const callSelector2 = web3.eth.abi.encodeFunctionSignature( "transferERC20ExactTo(bytes,address[7],uint8,uint256[6],bytes)" ); const callExtradata2 = web3.eth.abi.encodeParameters( ["address", "uint256", "address"], [erc20.address, fee1, carol] ); const callSelector3 = web3.eth.abi.encodeFunctionSignature( "transferERC20ExactTo(bytes,address[7],uint8,uint256[6],bytes)" ); const callExtradata3 = web3.eth.abi.encodeParameters( ["address", "uint256", "address"], [erc20.address, fee2, david] ); const extradataCall = web3.eth.abi.encodeParameters( ["address[]", "uint256[]", "bytes4[]", "bytes"], [ [statici.address, statici.address, statici.address], [ (callExtradata1.length - 2) / 2, (callExtradata2.length - 2) / 2, (callExtradata3.length - 2) / 2, ], [callSelector1, callSelector2, callSelector3], callExtradata1 + callExtradata2.slice("2") + callExtradata3.slice("2"), ] ); // Countercall should be an ERC721 transfer const selectorCountercall = web3.eth.abi.encodeFunctionSignature( "transferERC721Exact(bytes,address[7],uint8,uint256[6],bytes)" ); const extradataCountercall = web3.eth.abi.encodeParameters( ["address", "uint256"], [erc721.address, tokenId] ); const params = web3.eth.abi.encodeParameters( ["address[2]", "bytes4[2]", "bytes", "bytes"], [ [statici.address, statici.address], [selectorCall, selectorCountercall], extradataCall, extradataCountercall, ] ); selectorTwo = selector; extradataTwo = params; } const two = { registry: registry.address, maker: bob, staticTarget: statici.address, staticSelector: selectorTwo, staticExtradata: extradataTwo, maximumFill: amount, listingTime: "0", expirationTime: "10000000000", salt: "12", }; const sigTwo = await exchange.sign(two, bob); const firstData = erc721c.methods .transferFrom(alice, bob, tokenId) .encodeABI(); const c1 = erc20c.methods.transferFrom(bob, alice, amount).encodeABI(); const c2 = erc20c.methods.transferFrom(bob, carol, fee1).encodeABI(); const c3 = erc20c.methods.transferFrom(bob, david, fee2).encodeABI(); const secondData = atomicizerc.methods .atomicize( [erc20.address, erc20.address, erc20.address], [0, 0, 0], [(c1.length - 2) / 2, (c2.length - 2) / 2, (c3.length - 2) / 2], c1 + c2.slice("2") + c3.slice("2") ) .encodeABI(); const firstCall = { target: erc721.address, howToCall: 0, data: firstData }; const secondCall = { target: atomicizer.address, howToCall: 1, data: secondData, }; await exchange.atomicMatchWith( one, sigOne, firstCall, two, sigTwo, secondCall, ZERO_BYTES32, { from: carol } ); const [ aliceErc20Balance, carolErc20Balance, davidErc20Balance, tokenIdOwner, ] = await Promise.all([ erc20.balanceOf(alice), erc20.balanceOf(carol), erc20.balanceOf(david), erc721.ownerOf(tokenId), ]); assert.equal( aliceErc20Balance.toNumber(), amount, "Incorrect ERC20 balance" ); assert.equal(carolErc20Balance.toNumber(), fee1, "Incorrect ERC20 balance"); assert.equal(davidErc20Balance.toNumber(), fee2, "Incorrect ERC20 balance"); assert.equal(tokenIdOwner, bob, "Incorrect token owner"); }); ```Originally posted by @georgeroman in https://github.com/wyvernprotocol/wyvern-v3/issues/56#issuecomment-893203680